Event catalog
Every event type we send, and what each one carries.
Twenty-four event types, in eight groups. Every one of them maps to a real write in the product — nothing here fires from a place that does not exist, and nothing is emitted "for completeness".
Subscribe to the ones you need, or to * for everything including types added
later.
Orders
One fact, one event. An order rung up on a till, typed in at the desk, placed
on a storefront or posted through this API is the same fact — an order was
created — and all four emit order.created. Tell them apart with
data.object.source (admin, storefront, pos, channel, api) or
data.object.channel.
| Event | data.object | Fires when |
|---|---|---|
order.created | order | An order was placed on any surface. |
order.updated | order | The order status changed — whether a human moved it or a courier webhook did. Carries data.previous.status. |
order.paid | order | The order crossed into fully-paid. Edge-triggered: it fires on the transition, not on every subsequent payment. |
order.payment.created | order_payment | A payment row was written against an order. Refunds appear here too, as negative amounts — read data.object.amount, do not assume a sign. |
order.cancelled | order | The order moved to cancelled and its stock was released. |
order.fulfilled | order | The order reached delivered — the point where the sale is finalised and stock is committed. |
order.refund.created | order_payment | A refund was paid out against a return. |
Returns
| Event | data.object | Fires when |
|---|---|---|
return.created | return | A customer return was opened. |
return.updated | return | A return changed status (approved, received, completed, rejected). Carries data.previous.status. |
Shipping
| Event | data.object | Fires when |
|---|---|---|
shipment.created | shipment | A shipment was booked for an order, with or without a courier. |
shipment.updated | shipment | A shipment changed status — including the courier-webhook-driven transitions that also move the order. Carries data.previous.status. |
Inventory
| Event | data.object | Fires when |
|---|---|---|
inventory.adjusted | stock_movement | A manual stock adjustment was made (correction, damage, initial stock) — always with a reason. |
inventory.transferred | stock_transfer | Stock moved between two locations. One event for the whole transfer, never one per line. |
inventory.updated | variant level set | The sellable level for a variant at a location changed, for any reason. |
inventory.low_stock | inventory_level | A variant crossed its low-stock threshold at a location. |
Catalog
| Event | data.object | Fires when |
|---|---|---|
product.created | product | A product and its variants were created. |
product.updated | product | A product was edited. A bulk save emits one event per product, not one for the batch. |
product.archived | product | A product was archived and is no longer sellable. |
Customers
| Event | data.object | Fires when |
|---|---|---|
customer.created | customer | A customer record was created — including the find-or-create that happens at storefront checkout and on POST /orders. |
customer.updated | customer | A customer record was edited. |
customer.merged | customer_merge | Two customer records were merged. |
POS
| Event | data.object | Fires when |
|---|---|---|
pos.session.opened | pos_session | A cashier opened a register with a starting float. |
pos.session.closed | pos_session | A register was counted and closed. Carries the expected/counted/variance summary. |
System
| Event | data.object | Fires when |
|---|---|---|
salaf.ping | ping | Sent by the Send test event button. It rides the real pipeline — same signing, same retries, same delivery log — so a ping that arrives proves a real event would. See Testing. |
Object shapes
Most data.object shapes are the ones the REST API already publishes, field
for field:
| Object | Documented at |
|---|---|
| order | Orders |
| order_payment | Orders → List an order's payments |
| product | Products |
| customer | Customers |
| inventory_level | Inventory |
| stock_movement | Inventory |
Six shapes are webhook-only, because the v1 REST surface has no endpoint for
them yet. They follow the same conventions — snake_case, whitelisted fields,
money as two-decimal strings, ISO-8601 UTC timestamps — and when a REST
endpoint for them lands, it will publish exactly these:
return — id, store_id, order_id, order_number, number,
status, reason, location_id, refund_amount, restock, note,
received_at, refunded_at, created_at, updated_at, and items[] of
{ id, order_item_id, variant_id, quantity, restock }.
shipment — id, store_id, order_id, order_number, location_id,
courier (the slug, or null for a self-delivered shipment),
tracking_number, external_id (the courier's own consignment id), status,
shipping_charge, shipped_at, delivered_at, returned_at, created_at,
updated_at.
The inventory.updated variant level set — variant_id, store_id,
sku, total_on_hand, total_available (summed across locations), and
levels[], one full inventory-level object per location. Note that this is a
set, not a single level: a transfer changes two locations at once, and a
consumer receiving them separately would briefly believe stock had vanished.
The catalog endpoint reports this event's objectType as inventory_level,
which describes the entries in levels[] rather than the wrapper.
stock_transfer — reference_id (the id the two ledger entries share),
reference_type, store_id, from_location_id, to_location_id, note,
occurred_at, and lines[] of { variant_id, quantity }.
customer_merge — the full survivor customer object, plus
merged_from_id, moved (a count of the rows of each kind that moved across)
and merged_at.
pos_session — id, store_id, register ({ id, name, code }),
number, status, opened_at, opening_float, closed_at, created_at,
updated_at, and cash_summary — null while the session is open, and
otherwise { expected_cash, counted_cash, variance, variance_note, needs_approval }.
ping — store_id, message, sent_at.
Fetching the catalog programmatically
The dashboard's event picker, the validation that runs when you save an endpoint, and this page are all fed by one typed constant in the backend. You can read it too, from the dashboard API:
GET /api/v1/developer/webhook-endpoints/eventsIt answers with wildcard, the current apiVersion, and an events array of
{ type, group, label, description, objectType, carriesPrevious }.