SALAF EMSDevelopers
salafems.com

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.

Eventdata.objectFires when
order.createdorderAn order was placed on any surface.
order.updatedorderThe order status changed — whether a human moved it or a courier webhook did. Carries data.previous.status.
order.paidorderThe order crossed into fully-paid. Edge-triggered: it fires on the transition, not on every subsequent payment.
order.payment.createdorder_paymentA payment row was written against an order. Refunds appear here too, as negative amounts — read data.object.amount, do not assume a sign.
order.cancelledorderThe order moved to cancelled and its stock was released.
order.fulfilledorderThe order reached delivered — the point where the sale is finalised and stock is committed.
order.refund.createdorder_paymentA refund was paid out against a return.

Returns

Eventdata.objectFires when
return.createdreturnA customer return was opened.
return.updatedreturnA return changed status (approved, received, completed, rejected). Carries data.previous.status.

Shipping

Eventdata.objectFires when
shipment.createdshipmentA shipment was booked for an order, with or without a courier.
shipment.updatedshipmentA shipment changed status — including the courier-webhook-driven transitions that also move the order. Carries data.previous.status.

Inventory

Eventdata.objectFires when
inventory.adjustedstock_movementA manual stock adjustment was made (correction, damage, initial stock) — always with a reason.
inventory.transferredstock_transferStock moved between two locations. One event for the whole transfer, never one per line.
inventory.updatedvariant level setThe sellable level for a variant at a location changed, for any reason.
inventory.low_stockinventory_levelA variant crossed its low-stock threshold at a location.

Catalog

Eventdata.objectFires when
product.createdproductA product and its variants were created.
product.updatedproductA product was edited. A bulk save emits one event per product, not one for the batch.
product.archivedproductA product was archived and is no longer sellable.

Customers

Eventdata.objectFires when
customer.createdcustomerA customer record was created — including the find-or-create that happens at storefront checkout and on POST /orders.
customer.updatedcustomerA customer record was edited.
customer.mergedcustomer_mergeTwo customer records were merged.

POS

Eventdata.objectFires when
pos.session.openedpos_sessionA cashier opened a register with a starting float.
pos.session.closedpos_sessionA register was counted and closed. Carries the expected/counted/variance summary.

System

Eventdata.objectFires when
salaf.pingpingSent 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:

ObjectDocumented at
orderOrders
order_paymentOrders → List an order's payments
productProducts
customerCustomers
inventory_levelInventory
stock_movementInventory

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:

returnid, 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 }.

shipmentid, 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 setvariant_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_transferreference_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_sessionid, store_id, register ({ id, name, code }), number, status, opened_at, opening_float, closed_at, created_at, updated_at, and cash_summarynull while the session is open, and otherwise { expected_cash, counted_cash, variance, variance_note, needs_approval }.

pingstore_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/events

It answers with wildcard, the current apiVersion, and an events array of { type, group, label, description, objectType, carriesPrevious }.