Rather than polling for changes, register a URL and we POST to it when something happens. Deliveries are signed, retried, and logged where you can read them.Events#
| Event | Fires when |
|---|
order.created | A new order is placed |
order.paid | Payment is confirmed |
order.status_changed | Status moves — carries new_status and tracking_number, so this is also your shipping event |
product.created | A product is added |
product.updated | A product changes |
product.deleted | A product is removed |
That is the whole list. We do not accept subscriptions to events we do not publish, so anything you can subscribe to will actually arrive.Subscribe#
The signing secret is returned once. Store it with your other secrets — you need it to verify every delivery.Your URL must be public HTTPS. Loopback and private addresses are refused, so develop against a tunnel (ngrok, Cloudflare Tunnel) rather than localhost.What a delivery looks like#
{
"event": "order.paid",
"timestamp": "2026-01-31T12:00:00Z",
"data": {
"store_id": "0b7e2c1d-6f4a-4c8e-9a51-2d3f4e5a6b7c",
"order_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"order_number": "ORD-482913",
"payment_method": "cod",
"total": "850.00"
}
}
data.store_id names the store the event is about. X-NUMU-Delivery is stable across retries of the same delivery — use it to make your handler idempotent.Verify every delivery#
Compute HMAC-SHA256 over <timestamp>.<raw body> with your secret and compare against v1 in constant time. Verify against the raw body, before any JSON parsing or re-serialisation, or the bytes will differ and nothing will match.X-NUMU-Signature (sha256=… over the body alone) is still sent for older receivers. Prefer X-NUMU-Signature-V1: it is the one that stops a captured delivery being replayed at you later.Answer fast#
Reply 2xx within 10 seconds. Acknowledge first and do the work afterwards — a slow handler turns into a retry, and a retry you already processed turns into a duplicate.Retries#
A non-2xx or a timeout is retried 5 times: after 10s, 30s, 2m, 10m and 30m. The poller ticks every 60 seconds, so the first two rungs land on the next tick.After the last attempt the delivery is exhausted, your subscription is switched off, and the merchant is told in their dashboard. Fix the endpoint, then re-enable with PATCH {"is_active": true}.
Return 410 Gone to stop delivery immediately — we take it as permanent and do not retry.
Deliveries are at least once, and not ordered. order.paid can arrive before order.created. Key your handler on X-NUMU-Delivery and on the ids in the payload.
Manage a subscription#
| |
|---|
GET /stores/{store_id}/webhooks | List subscriptions |
PATCH /stores/{store_id}/webhooks/{id} | Change URL, events, description, or is_active |
POST /stores/{store_id}/webhooks/{id}/test | Deliver a signed webhook.ping now and see the response |
POST /stores/{store_id}/webhooks/{id}/rotate-secret | New signing secret, shown once |
GET /stores/{store_id}/webhooks/{id}/logs | Delivery attempts: status, response code, body, error |
DELETE /stores/{store_id}/webhooks/{id} | Remove it |
Rotation takes effect immediately — the old secret stops signing the moment the call returns, so deploy the new one promptly, or subscribe a second endpoint first if you cannot take a gap.Start with /test: it sends a real signed delivery, so an endpoint that passes it has a working signature check.Debugging#
GET …/logs is the first place to look. It records every attempt with the status code and the first part of your response body, which usually names the problem — a 401 means your signature check is rejecting us, a timeout means the handler is doing the work before answering.Partner apps#
A partner app does not subscribe through this API. Its subscriptions come from its manifest, and its deliveries are signed with its client secret. See App webhooks. Modified at 2026-09-24 13:03:12