Step 1: Publish lifecycle events
Core models publish*.created, *.updated and *.deleted automatically. Your models do too, with one line:
server/app/models/spree/brand.rb
brand.created, brand.updated and brand.deleted fire after the matching transaction commits. The payload is the record serialized with the v3 serializer you generated in step 1 — the events system resolves it by naming convention. Every consumer sees the same JSON shape your API serves, which is the point: one contract, not two.
Step 2: React inside the app
Generate a subscriber:config/initializers/spree.rb. Subscribers are not auto-discovered: one that never gets appended to Spree.subscribers is a silent no-op, which is why the generator owns that step. Re-runs are idempotent.
Fill in the handler:
server/app/subscribers/brand_sync_subscriber.rb
- The payload is the record, already serialized. It is the same JSON your API returns, so
event.payload['name']is there without a database query. IDs in it are public ones (brand_k5nR8xLq); when you do need the full record, look it up withSpree::Brand.find_by_prefix_id(event.payload['id'])rather than treating the id as a row number. - Subscribers run asynchronously by default. Each
handlecall is an ActiveJob, so a slow third-party API never blocks the request that triggered it. Passsubscribes_to 'brand.created', async: falseonly when you genuinely need it synchronous.
Step 3: Publish a custom event
Lifecycle events cover persistence. Custom events express domain moments — something meaningful happened, not just a row changed:Step 4: Deliver to external systems with webhooks
When the consumer is a system you do not deploy your own code into, use a webhook instead. In the dashboard go to Settings → Webhooks, add an endpoint URL, and select the events to deliver —brand.created and anything else publishing in your store.
Each delivery is an HTTP POST carrying the event name and the same serialized payload. The endpoint’s signing secret is shown once when you create it: store it in the receiving system and verify every request against it, so the receiver can prove the call came from your store.
Failed deliveries are retried with increasing delays, and the dashboard shows the delivery history per endpoint, which is where to look when a third party says they never received something.

