If you run a Shopify store with custom integrations, you've probably hit this: an order comes in, but your fulfillment system, inventory tracker, or CRM never gets notified. The order exists in Shopify, but it's invisible to the rest of your stack. The culprit is almost always a failed webhook.
Shopify's webhook system has specific constraints that make it surprisingly easy to lose events. This guide covers the exact failure modes, how to verify webhook signatures properly, and how to guarantee you never miss an order again.
Shopify's Webhook Constraints (The Ones That Bite You)
Shopify's webhook delivery system has three properties that combine to create reliability problems:
1. The 5-Second Timeout
Shopify gives your endpoint 5 seconds to respond with a 2xx status code. If your server takes longer — due to a slow database query, a cold start, or downstream API calls — Shopify marks the delivery as failed.
Five seconds sounds generous until you realize what happens during real production conditions:
- Your server is restarting during a deploy (10-30 second gap)
- Your database connection pool is exhausted during a flash sale
- You're calling a third-party API (shipping provider, ERP) synchronously in the handler
- Lambda/serverless cold start adds 2-3 seconds, leaving you 2 seconds for actual work
2. Limited Retry Window
When delivery fails, Shopify retries up to 19 times over 48 hours with increasing delays. After that, the webhook subscription is automatically marked as degraded. If failures continue, Shopify may delete your webhook subscription entirely — meaning you stop receiving ALL events of that type, not just the one that failed.
This is the most dangerous behavior: a temporary outage on your end can permanently break your integration if you don't catch it quickly.
3. Mandatory Webhook Topics
Starting in 2025, Shopify requires apps to subscribe to certain webhook topics (like app/uninstalled and compliance-related webhooks). If your app doesn't handle these properly, it can fail app review or lose API access. You can't afford to miss these events.
Verifying Shopify Webhook Signatures (HMAC-SHA256)
Every Shopify webhook includes an X-Shopify-Hmac-SHA256 header — a Base64-encoded HMAC digest of the request body, signed with your app's shared secret. You must verify this before processing any webhook.
Here's the correct implementation in Node.js:
import crypto from 'crypto';
function verifyShopifyWebhook(
rawBody: Buffer,
hmacHeader: string,
secret: string
): boolean {
const generatedHash = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('base64');
// Use timingSafeEqual to prevent timing attacks
try {
return crypto.timingSafeEqual(
Buffer.from(generatedHash),
Buffer.from(hmacHeader)
);
} catch {
return false; // Lengths don't match
}
}
// Express middleware
app.post('/webhooks/shopify',
express.raw({ type: 'application/json' }),
async (req, res) => {
const hmac = req.headers['x-shopify-hmac-sha256'] as string;
if (!verifyShopifyWebhook(req.body, hmac, process.env.SHOPIFY_WEBHOOK_SECRET!)) {
console.error('Shopify webhook signature verification failed');
return res.status(401).send('Unauthorized');
}
const event = JSON.parse(req.body.toString());
const topic = req.headers['x-shopify-topic'] as string;
// ACK immediately — process async
res.status(200).send('OK');
// Process in background
try {
await processShopifyEvent(topic, event);
} catch (err) {
console.error('Failed to process Shopify webhook:', topic, err);
// Without a reliability layer, this event may be lost
}
}
);
Critical detail: You must use the raw request body for HMAC verification, not a parsed-and-re-serialized version. If you use express.json() middleware and then JSON.stringify(req.body), the signature won't match because JSON serialization isn't deterministic (key ordering, whitespace).
The 5 Ways Shopify Webhooks Go Missing
1. Deploy Windows
Every time you deploy, there's a window where your server is unavailable. If Shopify sends an orders/create webhook during that window, it fails. With blue-green deployments you can minimize this, but you can't eliminate it entirely — especially with database migrations.
2. Synchronous Processing
The most common mistake: doing all your work inside the webhook handler before returning 200. If you're updating inventory, notifying a warehouse, and sending a customer email — all before responding — you'll blow past the 5-second timeout.
// BAD: synchronous processing
app.post('/webhooks/shopify/orders', async (req, res) => {
const order = req.body;
await updateInventory(order); // 1.5s
await notifyWarehouse(order); // 2s (external API)
await sendConfirmationEmail(order); // 1s
await updateCRM(order); // 1.5s
res.status(200).send('OK'); // Total: 6s — TIMEOUT
});
// GOOD: ACK first, process async
app.post('/webhooks/shopify/orders', async (req, res) => {
const order = req.body;
await db.webhookQueue.insert({ topic: 'orders/create', payload: order });
res.status(200).send('OK'); // <100ms
// Process via background job/queue worker
});
3. Unhandled Errors Crashing the Process
If your handler throws an unhandled exception, the HTTP connection drops without a response. Shopify sees this as a failure. If your error is deterministic (like a missing field in a new webhook format), every retry will fail the same way.
4. Scaling Issues During Flash Sales
Shopify can send hundreds of orders/create webhooks per minute during a flash sale. If your server can't keep up — connection pool exhaustion, memory pressure, CPU saturation — webhooks start timing out. Shopify's retry mechanism doesn't back off fast enough to help you recover, and the retries add even more load.
5. Webhook Subscription Deletion
This is the silent killer. If your endpoint fails consistently, Shopify doesn't just stop retrying the individual events — it removes the webhook subscription. New events aren't even attempted. You have to re-register the webhook, and the events that occurred during the gap are permanently lost.
Making Shopify Webhooks Bulletproof
There are two approaches: build it yourself or use a reliability layer.
DIY Approach
If you're building it yourself, you need:
- ACK immediately: Return 200 within 1 second, process asynchronously via a job queue (BullMQ, SQS, etc.)
-
Idempotency: Use the
X-Shopify-Webhook-Idheader as a dedup key — Shopify may deliver the same event multiple times - Reconciliation job: Periodically poll the Shopify Orders API to find orders that your system missed, comparing Shopify's records against your database
- Subscription monitoring: Check your webhook subscriptions daily using the Admin API — if one disappeared, re-register it and backfill from the API
- Dead letter queue: Events that fail processing after N retries need a place to go where you can inspect and replay them manually
That's a solid approach, but it's easily 2-3 weeks of engineering work to build and maintain properly.
Reliability Layer Approach
Put a webhook proxy between Shopify and your server. Instead of pointing Shopify at your endpoint directly, point it at a reliability layer that:
- Responds to Shopify within milliseconds (never times out)
- Stores every event durably before forwarding
- Retries delivery to your server with exponential backoff
- Provides a dead letter queue for manual inspection and replay
- Never lets Shopify see your endpoint's failures
This is what EventDock does. Because EventDock responds to Shopify in under 50ms from the nearest edge node, Shopify never sees a timeout. Your webhook subscription stays healthy even if your server has a bad day.
# Setup:
# 1. Create endpoint in EventDock dashboard
# Provider: Shopify
# Destination: https://yourapp.com/webhooks/shopify
# You get: https://in.eventdock.app/ep_xyz789
#
# 2. In Shopify Admin → Settings → Notifications → Webhooks:
# Point all webhook topics at: https://in.eventdock.app/ep_xyz789
#
# Now: Shopify → EventDock (instant ACK, durable storage)
# → Your server (retries, DLQ, monitoring)
#
# Your server can be slow, restart, or even go down.
# EventDock holds the events and delivers when you're ready.
Frequently Asked Questions
How long does Shopify retry failed webhooks?
Shopify retries up to 19 times over 48 hours with increasing delays. After all retries fail, the event is permanently lost. Worse, persistent failures can cause Shopify to delete your webhook subscription entirely, meaning you stop receiving all future events of that type.
Why is my Shopify webhook subscription disappearing?
Shopify automatically removes webhook subscriptions that consistently fail. If your endpoint returns errors or times out repeatedly, the subscription gets marked as degraded and eventually deleted. Using a webhook reliability layer prevents this by always responding to Shopify instantly, keeping your subscription healthy.
What is the Shopify webhook timeout limit?
5 seconds, strictly enforced and not configurable. Your endpoint must respond with a 2xx status within 5 seconds or the delivery is marked as failed. This is why synchronous processing in webhook handlers is dangerous.
How do I verify Shopify webhook signatures in Node.js?
Compute an HMAC-SHA256 digest of the raw request body using your app's shared secret, Base64-encode it, and compare it against the X-Shopify-Hmac-SHA256 header using crypto.timingSafeEqual(). The key detail is using the raw body — not a parsed and re-serialized version.
Can I recover missed Shopify webhooks?
You can poll Shopify's Admin API for resources (orders, products, etc.) and reconcile against your database. But this is a workaround, not a solution. It only works for resources with API endpoints, adds API rate limit pressure, and requires building a custom reconciliation system. Better to prevent loss in the first place with a reliability layer.
---
Never miss a Shopify order webhook again
EventDock sits between Shopify and your server, storing every event durably and delivering with automatic retries. Your Shopify webhook subscriptions stay healthy, even when your server doesn't.
5,000 events/month free. Set up in 2 minutes.
[Start Free](https://dashboard.eventdock.app/login)
Top comments (0)