Configure the webhook
An organization admin sets one URL and sees or rotates the signing secret inside the app. Nothing is sent until both the URL and secret exist.
Log in to create a keyorder.status_changed
Fires when an order status changes, for example paid, shipped, delivered or cancelled. Payload fields: event, order_id, order_number, previous_status, status, timestamp (ISO 8601).
{"event":"order.status_changed","order_id":"example-order-id","order_number":"SHP-EXAMPLE","previous_status":"pending_payment","status":"paid","timestamp":"2026-10-06T09:00:00Z"}Headers: Content-Type: application/json; X-Shiply-Event: order.status_changed; X-Shiply-Signature: sha256=<hex HMAC-SHA256 of the raw body using the webhook secret>.
Verify the raw body
import { createHmac, timingSafeEqual } from 'node:crypto';
// rawBody must be the original request bytes, NOT reserialized JSON.
export function verifySignature(rawBody, signatureHeader, secret) {
if (typeof signatureHeader !== 'string' ||
!/^sha256=[a-f0-9]{64}$/i.test(signatureHeader)) return false;
const expected = createHmac('sha256', secret).update(rawBody).digest();
const received = Buffer.from(signatureHeader.slice(7), 'hex');
return received.length === expected.length &&
timingSafeEqual(received, expected);
}Compute the HMAC over the original request bytes before JSON parsing. Compare equal-length digests using a constant-time comparison.
Delivery limits
One delivery attempt, no retry, and no customer-visible delivery log yet. There are no other webhook events and no published rate limits or SDKs.