How to receive, verify, test, and monitor Shopify webhooks — with HMAC-SHA256 verification code and common pitfalls.
Last updated March 2026. Covers Shopify API version 2025-10 and later.
Shopify webhooks notify your application in real time when events happen in a store — an order is placed, a product is updated, a customer signs up, or an app is uninstalled. Instead of polling the Shopify API, your server receives an HTTP POST with a JSON payload the moment the event occurs.
Common Shopify webhook event types:
The simplest way to register webhooks is through the Shopify Admin dashboard:
Order creation).Shopify will send a test POST to verify your endpoint is reachable. Make sure it returns a 200 status code.
You can also register webhooks programmatically via the Shopify Admin API using the POST /admin/api/2025-10/webhooks.json endpoint.
Shopify signs every webhook with HMAC-SHA256 using your app's shared secret. The signature is sent in the X-Shopify-Hmac-Sha256 header as a base64-encoded string. Always verify this before processing the payload.
const crypto = require('crypto');
const express = require('express');
const app = express();
const SHOPIFY_SECRET = process.env.SHOPIFY_WEBHOOK_SECRET;
app.post('/webhook/shopify',
express.raw({ type: 'application/json' }),
(req, res) => {
const hmacHeader = req.headers['x-shopify-hmac-sha256'];
// Compute HMAC from the raw body
const computed = crypto
.createHmac('sha256', SHOPIFY_SECRET)
.update(req.body)
.digest('base64');
// Constant-time comparison to prevent timing attacks
if (!crypto.timingSafeEqual(
Buffer.from(hmacHeader, 'utf8'),
Buffer.from(computed, 'utf8')
)) {
console.error('HMAC verification failed');
return res.status(401).send('Unauthorized');
}
// Signature verified — safe to process
const payload = JSON.parse(req.body);
const topic = req.headers['x-shopify-topic'];
console.log('Verified Shopify webhook:', topic);
switch (topic) {
case 'orders/create':
console.log('New order:', payload.id, payload.total_price);
break;
case 'app/uninstalled':
console.log('App uninstalled from shop:', payload.domain);
break;
default:
console.log('Unhandled topic:', topic);
}
res.status(200).json({ received: true });
}
);
app.listen(3000, () => console.log('Shopify webhook server on port 3000'));
Critical: use the raw request body for HMAC computation, not parsed JSON. If you use express.json() middleware globally, it will alter the body and break verification. Apply express.raw({ type: 'application/json' }) specifically on the webhook route.
During development, Shopify needs a publicly reachable URL to send webhooks. Here's the fastest way to test:
Order creation) and format JSON.You can also use the quick-send buttons in the tester to fire a simulated Shopify orders/create event without leaving the page.
In production, missed Shopify webhooks mean missed orders, stale inventory, or broken integrations. Set up proper monitoring:
| Issue | Cause | Fix |
|---|---|---|
| HMAC verification failed | Using parsed JSON body instead of raw body, or wrong shared secret | Use express.raw() on the webhook route. Verify your shared secret matches the one in Shopify Admin > Notifications > Webhooks. |
| Webhook removed after failures | Shopify automatically deletes webhooks after 19 consecutive failed deliveries | Monitor your endpoint uptime. Re-register the webhook via Admin or API after fixing the issue. |
| Duplicate events received | Shopify retries webhooks that don't receive a 200 response within 5 seconds | Return 200 immediately and process asynchronously. Store event IDs and check for duplicates. |
| Webhooks not arriving | Endpoint not publicly accessible, SSL/TLS issues, or firewall blocking Shopify IPs | Verify the URL is correct and reachable. Ensure a valid SSL certificate. Check firewall rules. |
| Timeout / slow response | Endpoint takes too long to respond. Shopify expects a response within 5 seconds. | Return 200 immediately. Move heavy processing (DB writes, API calls) to a background queue. |
Create a public endpoint and inspect Shopify payloads and HMAC behavior. No signup is needed for the tester.
Open Webhook Tester Monitor in ProductionRelated guides: What is a Webhook? · Webhook vs API · Webhook Security · Retry Strategies · Stripe · GitHub · Slack
More: What is a Webhook? | Webhook Security Guide | Stripe Webhook Guide | GitHub Webhook Guide | Compare Tools