Webhooks
Subscribe to store events, verify the signature, and handle retries.
Webhooks push store events to your endpoint so you don’t poll. Manage them under
/api/v1/webhooks (create, update, delete, rotate secret).
Events
Subscribe to any subset of:
| Event | Fires when |
|---|---|
order_created |
A new order is placed |
order_updated |
An order changes |
order_cancelled |
An order is cancelled |
order_shipped |
An order ships |
order_delivered |
An order is delivered |
payment_received |
A payment is captured |
product_created / product_updated |
Catalog changes |
customer_created / customer_updated |
Customer changes |
refund_created |
A refund is created |
Event names are underscore-cased
Use order_created, not order.created. Dot-cased names are rejected — the
value must match the platform’s event enum exactly.
Delivery format
Each delivery is an HTTP POST with a JSON body:
{
"event": "order_created",
"payload": { /* the resource */ },
"timestamp": "2026-08-06T10:20:30.000Z"
}
Redirects are not followed and the request times out at 10s.
Verifying the signature
Every delivery carries an HMAC signature header:
X-Webhook-Signature: sha256=<hex>
The signature is HMAC-SHA256(secret, JSON.stringify(payload)) — computed over the
payload object, using the secret shown once when you created the webhook
(rotate it anytime; the old secret stops verifying immediately).
import crypto from "crypto";
function verify(req) {
const expected =
"sha256=" +
crypto
.createHmac("sha256", process.env.WEBHOOK_SECRET)
.update(JSON.stringify(req.body.payload))
.digest("hex");
const got = req.headers["x-webhook-signature"];
return got && crypto.timingSafeEqual(Buffer.from(got), Buffer.from(expected));
}
Reject any request whose signature doesn’t match.
Retries & auto-disable
- Deliveries retry with exponential backoff (≈ 1 min, then 5 min) on failure.
- Only 5xx responses (and network/timeouts) are retried — a
4xxis treated as “you received it, you rejected it,” so return2xxto acknowledge. - After a streak of consecutive terminal failures the webhook is auto-disabled to stop hammering a dead endpoint; a successful delivery resets the streak.
Building a good receiver
- Respond fast (2xx) then process async. Acknowledge within the timeout; enqueue the work.
- Be idempotent. A retry can deliver the same event twice — dedupe on the resource id + event.
- Verify first. Check the signature before trusting the body.
Rotate on leak
If a secret leaks, rotate it from /api/v1/webhooks — the old secret stops
verifying at once. Update your receiver with the new secret shown at rotation.