Documentation
Developers5 min readUpdated 28 July 2026

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

EventFires when
order.createdAn order arrives from any channel
order.updatedStatus, lines or addresses change
order.routedRouting has assigned locations
order.fulfilledA shipment is recorded, in whole or in part
order.cancelledCancelled and reservations released
inventory.low_stockA variant crosses its threshold at a location
inventory.oversellA channel sold what was not available
product.updatedCatalog content, pricing or status changes
listing.rejectedA channel refused a listing, with its error text
payment.settledA 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))
}
Careful

Verify against the raw request body, before any JSON parsing or body-parser middleware. Re-serialising the payload changes the bytes and every signature will fail.

Writing a receiver that survives production

1
Return 2xx quickly

Acknowledge, then process on a queue. Handlers that do the work inline time out under load, which produces retries, which produces more load.

2
Deduplicate on event id

At-least-once delivery means duplicates are normal, not exceptional. Store the id and drop what you have already handled.

3
Tolerate out-of-order arrival

order.fulfilled can land before order.updated. Compare created_at and ignore anything older than the state you already hold.

4
Fail loudly on your own side

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.

Tip

Point a staging endpoint at production events while you build. Signature handling, ordering and duplicates are all much easier to get right against real traffic than against a fixture you wrote yourself.

Next: Plugins, for behaviour that belongs inside the platform rather than beside it.

Ask about this page withChatGPTClaudePerplexity