How to Set Up Shopify Webhooks: A Complete Developer Guide
Shopify's webhook system is powerful but has some quirks worth knowing about — HMAC verification, topic-based subscriptions, delivery guarantees, and handling the 5-second timeout. Here's the full setup guide.
Jason Warner
March 21, 2026
How to Set Up Shopify Webhooks: A Complete Developer Guide
Shopify webhooks let your application react to store events in near real-time: orders created, customers updated, products changed, payments processed. Setting them up is straightforward, but there are a few important details — particularly around signature verification and the response timeout — that you need to get right.
Common Shopify Webhook Topics
orders/create,orders/updated,orders/paid,orders/cancelled,orders/fulfilledcustomers/create,customers/update,customers/deleteproducts/create,products/update,products/deletecheckouts/create,checkouts/updateinventory_levels/updateapp/uninstalled
Creating a Webhook Subscription
Via the Admin UI
Settings → Notifications → Webhooks → Create webhook → select topic and enter your endpoint URL.
Via the REST API
POST /admin/api/2024-01/webhooks.json
{
"webhook": {
"topic": "orders/create",
"address": "https://your-app.com/webhooks/shopify/orders",
"format": "json"
}
}
Via GraphQL
mutation {
webhookSubscriptionCreate(
topic: ORDERS_CREATE
webhookSubscription: {
callbackUrl: "https://your-app.com/webhooks/shopify/orders"
format: JSON
}
) {
webhookSubscription { id }
userErrors { field message }
}
}
The 5-Second Rule
Shopify requires your endpoint to respond within 5 seconds. If it takes longer, Shopify marks it as failed and retries.
Respond immediately, process asynchronously:
app.post('/webhooks/shopify/orders', (req, res) => {
res.sendStatus(200); // respond immediately
processOrderAsync(req.body).catch(err => console.error(err));
});
HMAC Signature Verification
Every Shopify webhook includes X-Shopify-Hmac-Sha256 — a Base64-encoded HMAC-SHA256 of the request body, signed with your app's client secret. Always verify this.
const crypto = require('crypto');
function verifyShopifyWebhook(req, secret) {
const hmac = req.headers['x-shopify-hmac-sha256'];
const hash = crypto
.createHmac('sha256', secret)
.update(req.body) // MUST be raw buffer, not parsed JSON
.digest('base64');
return crypto.timingSafeEqual(Buffer.from(hmac), Buffer.from(hash));
}
app.post('/webhooks/shopify/orders',
express.raw({ type: 'application/json' }),
(req, res) => {
if (!verifyShopifyWebhook(req, process.env.SHOPIFY_SECRET)) {
return res.sendStatus(401);
}
res.sendStatus(200);
processOrderAsync(JSON.parse(req.body)).catch(console.error);
}
);
Critical: You must use the raw request body for HMAC. Use express.raw() or equivalent — not express.json().
Handling Retries and Idempotency
Shopify retries failed deliveries up to 19 times over 48 hours. Your handler must be idempotent. Use X-Shopify-Webhook-Id as your idempotency key:
const webhookId = req.headers['x-shopify-webhook-id'];
const alreadyProcessed = await db.processedWebhooks.findOne({ id: webhookId });
if (alreadyProcessed) return res.sendStatus(200);
await db.processedWebhooks.create({ id: webhookId });
// process...
Using Bluejay Relay for Shopify Webhooks
Point all your Shopify webhook topics at a single Bluejay Relay integration URL. Bluejay Relay handles:
- Signature verification: Built-in Shopify HMAC verification
- Fan-out: Deliver
orders/createto your order service, analytics, and fulfillment provider simultaneously - Retry: Configurable backoff if your downstream service is down
- Replay: Re-send historical orders without triggering new Shopify events
Set up Shopify webhook routing free on Bluejay Relay — handles verification, delivery logging, retry, and fan-out out of the box.
Build more reliable webhook workflows.
Capture, transform, and retry webhooks with full observability. Free to start, no credit card.