Skip to content
Home/Signed webhooks

Signed webhooks

One event. One delivery attempt. No retry.

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 key

order.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).

Example · JSON
{"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

Example · Node.js
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.