Overview
Spree includes a powerful event system that allows you to react to various actions happening in your store. When something happens (an order is completed, a product is created, etc.), Spree publishes an event that your code can subscribe to and handle. This pattern enables loose coupling between components and makes it easy to:- Send email notifications when orders are placed
- Sync data with external services when products change
- Log audit trails for compliance
- Trigger webhooks to notify third-party systems
- Update caches when inventory changes
How Events Work
Spree’s event system is built on top ofActiveSupport::Notifications but provides a cleaner, more Rails-like API through:
Spree::Events- The main module for publishing and subscribing to eventsSpree::Subscriber- Base class for creating event subscribersSpree::Publishable- Concern that enables models to publish events
Creating a Subscriber
Create a subscriber class inapp/subscribers/ that inherits from Spree::Subscriber:
app/subscribers/my_app/order_completed_subscriber.rb
Subscriber DSL
TheSpree::Subscriber class provides a clean DSL for declaring subscriptions:
Handling Multiple Events
When subscribing to multiple events, use theon DSL to route events to specific methods:
app/subscribers/my_app/order_audit_subscriber.rb
Working with Events
Event Object
When your subscriber receives an event, you get aSpree::Event object with:
Finding the Record
The payload contains serialized attributes, not the actual record. To get the record:Available Events
Lifecycle Events
Models that includeSpree::Publishable and call publishes_lifecycle_events automatically publish:
| Event Pattern | Description |
|---|---|
{model}.created | Record was created |
{model}.updated | Record was updated |
{model}.deleted | Record was deleted |
Spree::Price publishes price.created, price.updated, and price.deleted.
Models with lifecycle events enabled include: Order, Payment, Price, Shipment, Variant, LineItem, StockItem, and many others.
Order Events
| Event | Description |
|---|---|
order.created | Order was created |
order.updated | Order was updated |
order.completed | Order checkout completed |
order.canceled | Order was canceled |
order.resumed | Canceled order was resumed |
order.paid | Order is fully paid |
order.shipped | All order shipments are shipped |
Shipment Events
| Event | Description |
|---|---|
shipment.created | Shipment was created |
shipment.updated | Shipment was updated |
shipment.shipped | Shipment was shipped |
shipment.canceled | Shipment was canceled |
shipment.resumed | Shipment was resumed |
Payment Events
| Event | Description |
|---|---|
payment.created | Payment was created |
payment.updated | Payment was updated |
payment.paid | Payment was completed |
Price Events
| Event | Description |
|---|---|
price.created | Price was created |
price.updated | Price was updated |
price.deleted | Price was deleted |
Customer Events
| Event | Description |
|---|---|
customer.created | Customer was created |
customer.updated | Customer was updated |
customer.deleted | Customer was deleted |
Admin Events
| Event | Description |
|---|---|
admin.created | Admin user was created |
admin.updated | Admin user was updated |
admin.deleted | Admin user was deleted |
Product Events
| Event | Description |
|---|---|
product.activate | Product status changed to active |
product.archive | Product status changed to archived |
product.out_of_stock | Product has no stock left for any variant |
product.back_in_stock | Product was out of stock and now has stock again |
Publishing Custom Events
You can publish custom events from anywhere in your application:From a Model
Models includingSpree::Publishable can use publish_event:
From Anywhere
UseSpree::Events.publish directly:
Event Serializers
Event payloads are generated by dedicated serializer classes. Each model that publishes events needs a corresponding serializer in theSpree::Events namespace.
How Serializers Work
When a model publishes an event, Spree looks for a serializer class matching the model name:Spree::Order→Spree::Events::OrderSerializerSpree::Product→Spree::Events::ProductSerializerSpree::Payment→Spree::Events::PaymentSerializer
Built-in Serializers
Spree includes serializers for all core models inapp/serializers/spree/events/:
| Serializer | Model |
|---|---|
OrderSerializer | Orders with totals, states, timestamps |
ProductSerializer | Products with pricing and status |
PaymentSerializer | Payments with amounts and states |
ShipmentSerializer | Shipments with tracking info |
LineItemSerializer | Line items with quantity and pricing |
VariantSerializer | Variants with SKU and pricing |
| … | And many more |
Creating a Custom Serializer
For custom models or to add attributes to existing events, create a serializer:app/serializers/spree/events/order_serializer.rb
BaseSerializer Helpers
TheSpree::Events::BaseSerializer provides helpful methods:
| Method | Description |
|---|---|
resource | The model instance being serialized |
context | Hash with event context (event_name, triggered_at) |
money(amount) | Converts money to decimal value |
timestamp(time) | Formats time as ISO8601 string |
attribute(name) | Safely gets attribute (returns nil if missing) |
Overriding Built-in Serializers
To customize the payload for existing events, create your own serializer that overrides Spree’s:app/serializers/spree/events/order_serializer.rb
Serializers for Custom Models
If you add a custom model that publishes events, you must create a serializer:app/models/my_app/subscription.rb
app/serializers/spree/events/subscription_serializer.rb
Registering Subscribers
Subscribers inapp/subscribers/ are automatically registered during Rails initialization.
For subscribers in other locations, register them manually in an initializer:
config/initializers/event_subscribers.rb
Synchronous vs Asynchronous
By default, subscribers run asynchronously viaSpree::Events::SubscriberJob. This prevents slow subscriber code from blocking HTTP requests.
For critical operations that must complete before the request finishes, use synchronous mode:
Temporarily Disabling Events
You can disable event publishing temporarily:- Data migrations where you don’t want to trigger side effects
- Test setup where subscribers would interfere
- Bulk operations where individual events would be too noisy
Testing Subscribers
Testing Event Handling
spec/subscribers/my_app/order_completed_subscriber_spec.rb
Testing Event Publishing
Use theemit_webhook_event matcher (if available) or stub the events:
Best Practices
Keep handlers fast
Move slow operations to background jobs. Subscribers should do minimal work and delegate heavy lifting.
Handle missing records
Always check if the record exists before processing. It may have been deleted between event publish and handler execution.
Be idempotent
Design handlers to be safely re-run. Events might be delivered more than once in edge cases.
Use specific patterns
Subscribe to specific events rather than wildcards when possible. This makes code easier to understand and debug.
Example: Inventory Alert Subscriber
Here’s a complete example of a subscriber that sends alerts when inventory is low:app/subscribers/my_app/inventory_alert_subscriber.rb
Custom Event Adapters
Spree’s event system uses an adapter pattern, making it possible to swap the underlying event infrastructure. By default, Spree usesActiveSupport::Notifications, but you can create custom adapters for other backends like Kafka, RabbitMQ, or Redis Pub/Sub.
Configuring a Custom Adapter
Set your adapter class in an initializer:config/initializers/spree.rb
Creating a Custom Adapter
Inherit fromSpree::Events::Adapters::Base and implement the required methods:
app/models/my_app/events/kafka_adapter.rb
Base Class Interface
TheSpree::Events::Adapters::Base class defines the required interface:
| Method | Description |
|---|---|
publish(event_name, payload, metadata) | Publish an event, return Spree::Event |
subscribe(pattern, subscriber, options) | Register a subscriber for a pattern |
unsubscribe(pattern, subscriber) | Remove a subscriber |
activate! | Called during Rails initialization |
deactivate! | Called during shutdown |
build_event(name, payload, metadata)- Creates aSpree::Eventinstancesubscriptions_for(event_name)- Finds matching subscriptions from the registryregistry- Access to theSpree::Events::Registryinstance
See
Spree::Events::Adapters::ActiveSupportNotifications for a complete reference implementation.Related Documentation
- Webhooks - HTTP callbacks for external integrations

