Webhooks
Subscribe to events, verify signatures, and build a receiver that survives retries and out-of-order delivery.
On this page
Webhooks push events to you as they happen. If your integration polls a list endpoint on a timer, this page replaces it.
Events
| Event | Fires when |
|---|---|
order.created | An order arrives from any channel |
order.updated | Status, lines or addresses change |
order.routed | Routing has assigned locations |
order.fulfilled | A shipment is recorded, in whole or in part |
order.cancelled | Cancelled and reservations released |
inventory.low_stock | A variant crosses its threshold at a location |
inventory.oversell | A channel sold what was not available |
product.updated | Catalog content, pricing or status changes |
listing.rejected | A channel refused a listing, with its error text |
payment.settled | A settlement matched to an order |
Subscribe per endpoint, per event type. Several endpoints can receive the same event.
Payload
{
"id": "evt_01HQ3M8ZK9",
"type": "order.created",
"created_at": "2026-07-28T10:14:22Z",
"project_id": "prj_01HQ0000",
"data": {
"object": "order",
"id": "ord_01HQ3M8ZK9",
"channel_id": "chn_marketplace_eu",
"total": { "amount": 12900, "currency": "EUR" }
}
}
The payload carries enough to route and deduplicate the event. Fetch the full record from the API when you need the rest — payloads are a notification, not a replica of your database.
Verifying the signature
Every request carries a timestamp and an HMAC-SHA256 signature over timestamp.body, computed with your endpoint's signing secret.
import crypto from 'crypto'
export function verify(rawBody, header, secret) {
const [timestamp, signature] = header.split(',').map(part => part.split('=')[1])
// Reject anything older than five minutes: a valid signature replayed later is still a replay.
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false
const expected = crypto.createHmac('sha256', secret).update(`${timestamp}.${rawBody}`).digest('hex')
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature))
}
Writing a receiver that survives production
Acknowledge, then process on a queue. Handlers that do the work inline time out under load, which produces retries, which produces more load.
At-least-once delivery means duplicates are normal, not exceptional. Store the id and drop what you have already handled.
order.fulfilled can land before order.updated. Compare created_at and ignore anything older than the state you already hold.
A 500 from your endpoint triggers our retry, which is correct. Silently swallowing an error and returning 200 loses the event permanently.
Retries and replay
Failed deliveries retry with exponential backoff for 24 hours. Anything still failing after that is kept in the delivery log, where each attempt shows the response code and body, and can be replayed by hand or in bulk once your endpoint recovers.
Next: Plugins, for behaviour that belongs inside the platform rather than beside it.




