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 provides a clean 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 using the same Store API V3 serializers used by the REST API. This means webhook payloads and API responses share the same schema, making it easy to reuse types in your integrations.How Serializers Work
When a model publishes an event, Spree looks for a V3 serializer class matching the model name:Spree::Order→Spree::Api::V3::OrderSerializerSpree::Product→Spree::Api::V3::ProductSerializerSpree::Payment→Spree::Api::V3::PaymentSerializer
Spree::Exports::Products), the serializer lookup walks up the class hierarchy until it finds a match (e.g., → Spree::Api::V3::ExportSerializer).
If no serializer is found, a minimal fallback payload is returned:
Built-in Serializers
Spree includes V3 serializers for all core models inapi/app/serializers/spree/api/v3/:
| Serializer | Model |
|---|---|
OrderSerializer | Orders with totals, states, nested line items, shipments, payments, addresses |
ProductSerializer | Products with pricing, stock status, availability |
PaymentSerializer | Payments with amounts, states, nested payment method and source |
ShipmentSerializer | Shipments with tracking, nested shipping method and rates |
LineItemSerializer | Line items with quantity, pricing, nested option values |
VariantSerializer | Variants with SKU, pricing, nested option values |
PriceSerializer | Prices with amounts, currency, price list |
| … | And many more |
Payload Context
Event serializers receive specific context parameters that control what data is included:store— Prefers the resource’s store (e.g.,order.store), falls back toSpree::Current.storecurrency— UsesSpree::Current.currency(with full fallback chain)user: nil— Events never include user-specific pricingincludes: []— Conditional associations are not included in event payloads
Overriding Event Serializers
To customize the payload for existing events, create a custom V3 serializer and configure it via dependencies:app/serializers/my_app/order_serializer.rb
config/initializers/spree.rb
Serializers for Custom Models
If you add a custom model that publishes events, create a V3 serializer:app/models/my_app/subscription.rb
app/serializers/spree/api/v3/subscription_serializer.rb
id, created_at, and updated_at.
Registering Subscribers
Subscribers inapp/subscribers/ are automatically registered during application initialization.
For subscribers in other locations, add them to the Spree.subscribers array in an initializer:
config/initializers/event_subscribers.rb
config/initializers/event_subscribers.rb
Synchronous vs Asynchronous
By default, subscribers run asynchronously via a background job. 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 application 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
- Customization Quickstart - Overview of all customization options
- Decorators - When to use decorators vs events
- Checkout Flow - Using events in checkout customization

