Skip to main content

Order webhooks for developers

A store's managers add an endpoint under Notifications → Order webhooks in the Products hub, choose its events, and copy its signing secret — see Order webhooks for that side and the list of events. This page is for whoever builds the endpoint.

What your endpoint receives​

A POST with a JSON body:

{
"id": "evt_…",
"type": "order.fulfilled",
"createdAt": "2026-10-07T15:04:05.000Z",
"siteId": "…",
"data": {
"order": { "id": "…", "object": "order", "number": 1042, "status": "partially_fulfilled", "…": "…" },
"fulfillment": { "id": "…", "lines": [{ "lineItemId": 0, "quantity": 1 }], "carrier": "UPS", "trackingNumber": "1Z…", "trackingUrl": "https://…" }
}
}

data.order is the order in the same shape the API returns for GET /v1/sites/{siteId}/orders/{orderId}. Money is in whole cents. Three headers come with it:

HeaderWhat it holds
Aglyn-EventThe event, e.g. order.paid.
Aglyn-Event-IdThe event's id. It is the same on every retry, so store it and ignore an id you have already handled.
Aglyn-Signaturet=<timestamp>,v1=<signature>.

Check the signature​

The signature is the HMAC-SHA256, in hex, of the timestamp, a period, and the raw request body, keyed with your signing secret. Check it against the raw body before parsing it, and refuse a timestamp more than five minutes old:

import { createHmac, timingSafeEqual } from 'node:crypto'

function verify(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(',').map((part) => part.split('=')))
const expected = createHmac('sha256', secret).update(`${parts.t}.${rawBody}`).digest('hex')
const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300
return fresh && timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1 ?? ''))
}

Answer quickly, and retries​

Answer with any 2xx status within 8 seconds. Anything else — another status, a redirect, a timeout — is a failed attempt, and the event is sent again after 30 seconds, 2 minutes, 10 minutes, 30 minutes, 1 hour, 6 hours and 12 hours: eight attempts over about a day. If the last one fails, the site's managers are told. An event can arrive more than once, and events about one order can arrive out of order, so use Aglyn-Event-Id to skip repeats and the order's status rather than the arrival order.