Handling Shopify Webhooks: A Complete Guide
Learn how to receive and process Shopify webhooks reliably. This guide covers webhook topics, registration methods, HMAC verification, common challenges, and best practices for e-commerce integrations.

Handling Shopify Webhooks: A Complete Guide
Shopify webhooks let your application respond instantly to store events—orders, products, customers—instead of constantly polling the API. Handling them reliably requires understanding registration methods, signature verification, and retry behavior.
This guide covers everything e-commerce developers need: practical code examples and solutions to common production challenges.
Understanding Shopify Webhook Topics
Shopify organizes webhooks around topics—specific events that trigger notifications. Choose the right topics to receive only the data you need.
Order Webhooks
Order webhooks are the most commonly used for e-commerce integrations:
- orders/create: Fires when a customer completes checkout. Use this to trigger fulfillment workflows, update external inventory systems, or send order data to your ERP.
- orders/updated: Triggers when any order attribute changes, including status updates, address edits, or line item modifications.
- orders/paid: Specifically fires when payment is captured—useful for apps that should only act on confirmed payments.
- orders/cancelled: Notifies when orders are cancelled, enabling automatic refund processing or inventory restoration.
- orders/fulfilled: Triggers when all items in an order are marked as fulfilled.
Product Webhooks
Product webhooks keep external systems synchronized with your catalog:
- products/create: Fires when new products are added to the store.
- products/update: Triggers on any product change—title, description, price, variants, images, or inventory levels.
- products/delete: Notifies when products are removed from the catalog.
Customer Webhooks
Customer webhooks power personalization and CRM integrations:
- customers/create: Fires when new customer accounts are created.
- customers/update: Triggers on profile changes, including address updates and marketing preferences.
- customers/delete: Notifies when customer data is removed (important for GDPR compliance).
Fulfillment Webhooks
Fulfillment webhooks enable shipping and logistics integrations:
- fulfillments/create: Fires when fulfillment records are created for orders.
- fulfillments/update: Triggers when tracking information is added or fulfillment status changes.
Registering Shopify Webhooks
Shopify provides two registration methods, each suited to different use cases.
Method 1: Shopify Admin API
Register webhooks programmatically using the Admin API for custom apps and integrations.
const Shopify = require('@shopify/shopify-api');
async function registerWebhook(session) {
const response = await Shopify.Webhooks.Registry.register({
shop: session.shop,
accessToken: session.accessToken,
path: '/webhooks/orders',
topic: 'ORDERS_CREATE',
webhookHandler: async (topic, shop, body) => {
const order = JSON.parse(body);
console.log(`New order ${order.id} from ${shop}`);
// Process the order
},
});
if (response.success) {
console.log(`Webhook registered for ${response.result.topic}`);
} else {
console.error(`Failed to register webhook: ${response.result}`);
}
}Using the REST API directly:
const axios = require('axios');
async function createWebhook(shop, accessToken, topic, address) {
const response = await axios.post(
`https://${shop}/admin/api/2024-01/webhooks.json`,
{
webhook: {
topic: topic,
address: address,
format: 'json'
}
},
{
headers: {
'X-Shopify-Access-Token': accessToken,
'Content-Type': 'application/json'
}
}
);
return response.data.webhook;
}
// Register multiple webhooks
const topics = ['orders/create', 'orders/updated', 'products/update'];
for (const topic of topics) {
await createWebhook(shop, token, topic, 'https://yourapp.com/webhooks/shopify');
}Method 2: Shopify Partner Dashboard
For public apps distributed through the Shopify App Store, configure webhooks in your Partner Dashboard under App Setup. This method automatically registers webhooks when merchants install your app.
Navigate to your app in the Partner Dashboard, select "App setup," and add webhook subscriptions. Specify the callback URL and select which topics your app needs. Shopify handles registration during the OAuth installation flow.
Mandatory Webhooks for App Store Apps
If you're building a public Shopify app, certain webhooks are mandatory for App Store approval:
- customers/data_request: Handle customer data export requests (GDPR)
- customers/redact: Process customer data deletion requests (GDPR)
- shop/redact: Clean up shop data when merchants uninstall your app
These compliance webhooks must return a 200 status within specific timeframes, even if processing happens asynchronously.
HMAC Signature Verification
Every Shopify webhook includes an HMAC-SHA256 signature in the X-Shopify-Hmac-Sha256 header. Verifying this signature confirms the webhook originated from Shopify and wasn't tampered with. See our HMAC-SHA256 webhook signatures guide for detailed explanation.
Node.js Verification Implementation
const crypto = require('crypto');
function verifyShopifyWebhook(rawBody, hmacHeader, secret) {
const generatedHash = crypto
.createHmac('sha256', secret)
.update(rawBody, 'utf8')
.digest('base64');
return crypto.timingSafeEqual(
Buffer.from(generatedHash),
Buffer.from(hmacHeader)
);
}
// Express.js middleware
const express = require('express');
const app = express();
app.post('/webhooks/shopify',
express.raw({ type: 'application/json' }),
(req, res) => {
const hmac = req.headers['x-shopify-hmac-sha256'];
const secret = process.env.SHOPIFY_WEBHOOK_SECRET;
if (!verifyShopifyWebhook(req.body, hmac, secret)) {
console.error('Webhook verification failed');
return res.status(401).send('Unauthorized');
}
// Parse and process the verified webhook
const data = JSON.parse(req.body.toString());
const topic = req.headers['x-shopify-topic'];
const shop = req.headers['x-shopify-shop-domain'];
console.log(`Received ${topic} from ${shop}`);
// Acknowledge immediately, process async
res.status(200).send('OK');
// Queue for background processing
processWebhookAsync(topic, shop, data);
}
);Critical: Use express.raw() to access the raw request body. If you use express.json(), the body gets parsed and re-serialization won't match the original bytes, causing verification failures.
Complete Verification Middleware
const verifyShopifyWebhookMiddleware = (req, res, next) => {
const hmac = req.headers['x-shopify-hmac-sha256'];
if (!hmac) {
return res.status(401).json({ error: 'Missing HMAC header' });
}
const secret = process.env.SHOPIFY_WEBHOOK_SECRET;
const generatedHash = crypto
.createHmac('sha256', secret)
.update(req.body, 'utf8')
.digest('base64');
const isValid = crypto.timingSafeEqual(
Buffer.from(generatedHash),
Buffer.from(hmac)
);
if (!isValid) {
console.error('HMAC verification failed', {
shop: req.headers['x-shopify-shop-domain'],
topic: req.headers['x-shopify-topic']
});
return res.status(401).json({ error: 'Invalid signature' });
}
// Attach parsed data and metadata to request
req.webhookData = JSON.parse(req.body.toString());
req.webhookTopic = req.headers['x-shopify-topic'];
req.webhookShop = req.headers['x-shopify-shop-domain'];
next();
};Common Challenges and Solutions
Challenge 1: Webhook Delivery Failures
Shopify expects a 2xx response within 5 seconds. Slow or unavailable endpoints trigger failed delivery and retries. See webhook retry strategies for handling retries gracefully.
Solution: Acknowledge webhooks immediately and process asynchronously.
app.post('/webhooks/shopify', verifyMiddleware, async (req, res) => {
// Respond immediately
res.status(200).send('OK');
// Queue for background processing
await webhookQueue.add('process-webhook', {
topic: req.webhookTopic,
shop: req.webhookShop,
data: req.webhookData,
receivedAt: new Date().toISOString()
});
});Challenge 2: Duplicate Webhooks
Shopify may send the same webhook multiple times during retries or network issues. Your handlers must be idempotent, especially for order webhooks where duplicates affect inventory or fulfillment. See our webhook idempotency guide for detailed patterns.
Solution: Track processed webhook IDs and skip duplicates.
async function processWebhook(topic, shop, data) {
const webhookId = `${shop}-${topic}-${data.id}-${data.updated_at}`;
// Check if already processed
const exists = await redis.get(`webhook:${webhookId}`);
if (exists) {
console.log(`Skipping duplicate webhook: ${webhookId}`);
return;
}
// Mark as processing
await redis.setex(`webhook:${webhookId}`, 86400, 'processing');
try {
// Process the webhook
await handleWebhookByTopic(topic, data);
// Mark as completed
await redis.setex(`webhook:${webhookId}`, 86400, 'completed');
} catch (error) {
// Remove lock on failure to allow retry
await redis.del(`webhook:${webhookId}`);
throw error;
}
}Challenge 3: Rate Limits
Shopify applies rate limits to webhook registrations (not deliveries). Implement backoff strategies when registering webhooks for many stores.
Solution: Batch registrations and respect rate limit headers.
async function registerWebhooksWithRateLimit(shops, topics) {
for (const shop of shops) {
for (const topic of topics) {
try {
await createWebhook(shop.domain, shop.token, topic, callbackUrl);
} catch (error) {
if (error.response?.status === 429) {
const retryAfter = error.response.headers['retry-after'] || 2;
await sleep(retryAfter * 1000);
// Retry once
await createWebhook(shop.domain, shop.token, topic, callbackUrl);
} else {
throw error;
}
}
// Small delay between registrations
await sleep(100);
}
}
}Challenge 4: Missing Webhooks
Webhooks don't arrive due to endpoint issues, Shopify outages, or misconfiguration. Critical processes shouldn't rely solely on webhooks.
Solution: Implement reconciliation jobs that poll the API periodically.
async function reconcileOrders(shop, accessToken, since) {
const orders = await fetchOrdersSince(shop, accessToken, since);
for (const order of orders) {
const processed = await redis.get(`order:${order.id}`);
if (!processed) {
console.log(`Reconciliation found missed order: ${order.id}`);
await processOrder(order);
}
}
}
// Run reconciliation every hour
setInterval(() => {
const oneHourAgo = new Date(Date.now() - 3600000).toISOString();
reconcileOrders(shop, token, oneHourAgo);
}, 3600000);Best Practices for Production
Use Message Queues
Never process webhooks synchronously in the HTTP handler. Use a message queue like Redis, RabbitMQ, or SQS to decouple receipt from processing.
const Queue = require('bull');
const webhookQueue = new Queue('shopify-webhooks', process.env.REDIS_URL);
webhookQueue.process('orders/create', async (job) => {
const { shop, data } = job.data;
await syncOrderToERP(shop, data);
await updateInventory(data.line_items);
await notifyFulfillment(data);
});
webhookQueue.on('failed', (job, err) => {
console.error(`Webhook job failed: ${job.id}`, err);
// Alert on repeated failures
if (job.attemptsMade >= 3) {
alertOps(`Webhook processing failed after 3 attempts: ${job.id}`);
}
});Implement Comprehensive Logging
Log every webhook with enough context for debugging but avoid logging sensitive customer data.
function logWebhook(topic, shop, data, status) {
console.log(JSON.stringify({
timestamp: new Date().toISOString(),
type: 'webhook',
topic,
shop,
resourceId: data.id,
status,
// Don't log PII
}));
}Monitor Delivery Health
Track webhook processing metrics: success rates, latency, error types. Set up alerts for anomalies that might indicate integration issues.
Simplify with Hook Mesh
Hook Mesh handles operational overhead—queue infrastructure, retry logic, monitoring, multi-tenant routing. For Shopify integrations specifically:
- Automatic deduplication based on webhook IDs and timestamps
- Intelligent routing to different handlers based on topic and shop
- Failure alerting when delivery issues affect specific stores
- Replay capability to reprocess webhooks after fixing bugs
Focus on application logic instead of webhook infrastructure.
Conclusion
Production-ready Shopify webhook implementations require careful attention to verification, idempotency, and reliability. Use HMAC verification for every webhook, process asynchronously with message queues, handle duplicates gracefully, and implement reconciliation for critical data.
For multi-channel e-commerce, you may also need payment webhooks from providers like Stripe. For a complete overview, visit our platform integrations hub.
Related Posts
How to Receive Stripe Webhooks Reliably
A comprehensive guide to setting up, verifying, and handling Stripe webhooks in production. Learn best practices for idempotency, event ordering, and building reliable webhook endpoints with Node.js and Python examples.
HMAC-SHA256 Webhook Signatures: Implementation Guide
Learn how to implement secure webhook signature verification using HMAC-SHA256. Complete guide with code examples for signing and verifying webhook payloads in Node.js and Python.
Webhook Idempotency: Why It Matters and How to Implement It
A comprehensive technical guide to implementing idempotency for webhooks. Learn about idempotency keys, deduplication strategies, and implementation patterns with Node.js and Python code examples.
Webhook Retry Strategies: Linear vs Exponential Backoff
A technical deep-dive into webhook retry strategies, comparing linear and exponential backoff approaches, with code examples and best practices for building reliable webhook delivery systems.
Webhook Integration Guides: Connect with Any Platform
Comprehensive guides for integrating webhooks from Stripe, GitHub, Shopify, Twilio, and 10+ other platforms. Learn best practices for reliable webhook handling across your entire stack.