Skip to main content

Listen for order events

Subscribe to order and fulfillment webhooks to drive your app as an order moves through its lifecycle. Signature verification, retries, and headers are covered once in Webhook Overview — this recipe focuses on order events.

When to use this

Syncing order status, triggering fulfillment, or reconciling refunds.

Prerequisites

  • A working public app with OAuth — follow Develop a standalone app. Every request below carries an Access-Token: {token} header, using the token you saved for each shop.
  • Read Webhook Overview first.

Order lifecycle events

TopicFires whenReference
orders/createAn order is placedorders/create
orders/paidAn order is paidorders/paid
orders/fulfilledAn order is fully fulfilledorders/fulfilled
orders/partially_fulfilledPart of an order is fulfilledorders/partially_fulfilled
orders/cancelledAn order is cancelledorders/cancelled
orders/refundedAn order is refundedorders/refunded
fulfillments/createA fulfillment is createdfulfillments/create
fulfillments/updateA fulfillment is updatedfulfillments/update

Subscribe

Register one subscription per topic — call this once for each of the order and fulfillment topics listed above. From 2026-01, the request body wraps the subscription in a webhook object.

POST /openapi/2026-01/webhooks

Request body:

{
"webhook": {
"topic": "orders/paid", // one call per topic
"address": "https://your-app.example.com/webhook/orders-paid"
}
}

Returns the created subscription object. See Webhook Overview for version differences and for cleaning up stale subscriptions.

Receive, verify, and route

Verify over the raw body, then route by topic to the actions in the other recipes.

const express = require('express');
const crypto = require('crypto');
const app = express();

function verifyWebhook(rawBody, hmacHeader, clientSecret) {
const digest = crypto.createHmac('sha256', clientSecret).update(rawBody).digest('base64');
return crypto.timingSafeEqual(Buffer.from(digest), Buffer.from(hmacHeader));
}

// Apply express.raw() on the webhook route only — verification needs the raw bytes.
app.post('/webhook/:event', express.raw({ type: '*/*' }), async (req, res) => {
if (!verifyWebhook(req.body, req.get('X-Shoplazza-Hmac-Sha256'), CLIENT_SECRET)) {
return res.sendStatus(401);
}
const shop = req.get('X-Shoplazza-Shop-Domain');
const topic = req.get('X-Shoplazza-Topic');
const payload = JSON.parse(req.body.toString('utf8'));
// orders/* carry order; fulfillments/* carry fulfillment (with order_id)
const orderId = payload.order?.id || payload.fulfillment?.order_id;
// ... look up the shop token, fetch the order (see Manage orders), react by topic ...
res.sendStatus(200);
});
warning

Verify against the raw, unparsed body. Apply express.raw() on the webhook route only — a global JSON parser changes the bytes and verification always fails.

Next steps