WebhooksAugust 3, 20268 min read

Shopify Deleted Your Webhook Subscription. Here's Why 5 Seconds Is the Whole Story.

Shopify gives your endpoint five seconds to respond, retries eight times over four hours, then removes the subscription entirely if failures continue. Nothing throws. Orders just stop arriving. Here is how to survive the 5-second budget.

The symptom is unusually quiet. Orders stop appearing in your app. No errors in your logs, because nothing is reaching your logs. No failed deliveries in the Shopify admin, because there is no subscription left to show deliveries for. Shopify removed it, and unless the warning email reached a mailbox somebody reads, the first signal is a merchant asking why their last two days of orders are missing.

Shopify's webhook rules are stricter than most, and the strictness is concentrated in one number.

TL;DR

  • Five seconds. That is your entire response budget, per delivery
  • Shopify retries 8 times over 4 hours; if failures persist over roughly the next 48 hours the subscription is deleted
  • Deletion is silent from your app's perspective. No traffic and no errors, because there is no subscription left
  • Warning and deletion emails go to your Partner account's emergency developer email, which is usually the address nobody monitors
  • Recovery means re-creating the subscription and backfilling with the Admin API, because Shopify does not replay

Where the five seconds actually goes

Stripe gives you tens of seconds. GitHub is comparably relaxed. Shopify waits five seconds for a response and treats anything slower as a failed delivery.

Five seconds sounds like plenty until you write down what actually happens inside a typical handler:

StepTypicalBad day
Cold start (serverless)200ms2.5s
HMAC verification2ms2ms
Look up shop by domain30ms400ms
Write order + line items80ms1.2s
Sync to ERP / 3PL600mstimeout
Send confirmation email250ms3s

The happy path is 1.2 seconds and looks completely healthy. Then your ERP has a slow morning, every delivery crosses five seconds simultaneously, and you burn all eight retries inside four hours. Nothing in your code changed. Nothing in your code is wrong. The subscription is gone by Thursday.

Two aggravating factors specific to Shopify:

Flash sales correlate failures. Webhooks arrive in proportion to orders. The moment you most need orders/create is the moment your database is busiest and your response times are worst. The failure mode is precisely anti-correlated with when you can afford it.

Bulk operations fan out hard. A merchant editing 5,000 products through an app or a CSV import generates 5,000 products/update webhooks in a short window. Your endpoint is now rate-limited by your own database connection pool, and every delivery over five seconds counts against you.

What deletion actually looks like

After eight failed attempts within the four-hour window, Shopify stops retrying that event. If deliveries keep failing over roughly the next 48 hours, the subscription itself is removed.

Shopify does email you: a warning when repeated failures are detected, and a second when the subscription is deleted. Both go to the emergency developer email on your Partner account. In practice that address was set once during onboarding, often to a personal address of someone who has since left, and it is the single most common reason a team is surprised by this.

Go check what that address is right now. It takes thirty seconds and it is the highest-value thing in this post.

After deletion:

  • webhookSubscriptions returns nothing for that topic
  • No deliveries and no failures, because there is nothing left to fail
  • Events generated while the subscription was gone are not queued anywhere. Shopify does not replay after re-creation

That last point is what makes this expensive. Unlike a Stripe endpoint being disabled, where the events still exist in the API for 30 days, a deleted Shopify subscription means those webhooks were simply never sent. Recovery is a reconciliation job against the Admin API, not a replay.

Getting under five seconds

The only reliable shape is: verify, persist, acknowledge, and do the real work somewhere else.

export async function POST(req: Request) {
  const raw = await req.text();               // raw body, required for HMAC
  if (!verifyShopifyHmac(raw, req.headers.get("x-shopify-hmac-sha256"))) {
    return new Response("unauthorized", { status: 401 });
  }

  await queue.publish({                       // durable write, ~10ms
    topic: req.headers.get("x-shopify-topic"),
    shop: req.headers.get("x-shopify-shop-domain"),
    webhookId: req.headers.get("x-shopify-webhook-id"),
    body: raw,
  });

  return new Response(null, { status: 200 }); // well inside 5s
}

Four details worth being precise about:

Read the raw body, not parsed JSON. Shopify's HMAC is computed over the exact bytes. Any middleware that parses and re-serializes will change key order or whitespace and every signature check will fail. On Next.js App Router, await req.text() before any parsing. See Verifying Stripe, GitHub, and Shopify Webhook Signatures for the per-provider details.

Verify before queueing, not after. Verification is ~2ms and it's the gate that stops an attacker filling your queue. Cheap gate first.

Dedupe on X-Shopify-Webhook-Id. It is stable across all retries of the same event, which makes it the correct idempotency key. Shopify will re-deliver, so your consumer must be idempotent. The patterns are in You Received the Same Webhook Twice.

Watch cold starts. On serverless, a cold start can eat half your budget before your code runs. Keep the webhook route's dependency graph small; importing your entire ORM and admin SDK into a handler that only needs to enqueue bytes is a real and avoidable cost.

Recovering after a deletion

1. Re-create the subscription.

mutation {
  webhookSubscriptionCreate(
    topic: ORDERS_CREATE
    webhookSubscription: {
      callbackUrl: "https://your-app.example.com/webhooks/shopify"
      format: JSON
    }
  ) {
    webhookSubscription { id }
    userErrors { field message }
  }
}

2. Reconcile the gap from the Admin API. Since nothing is replayed, query the objects directly for the outage window and process them through the same code path your webhook consumer uses:

{
  orders(first: 250, query: "updated_at:>2026-07-28T00:00:00Z") {
    edges { node { id name updatedAt displayFinancialStatus } }
  }
}

Run that through your idempotent consumer. Anything already handled is a no-op; anything missed gets applied. This is also the moment you discover whether your consumer really is idempotent.

3. Audit which topics you lost. List current subscriptions and diff against what your app expects. Deletion is per-subscription, so it is entirely normal to lose orders/create while products/update survived, which makes the symptom even more confusing.

4. Fix the emergency developer email, and add your own monitoring on top. A "no orders/create received in 6 hours during business hours" alert would have caught this on day one.

Where AnyHook fits

The five-second budget is a transport problem, and transport is exactly what a relay replaces.

Point the Shopify subscription at in.anyhook.net/you/shopify. AnyHook returns 200 in under 50ms, every time, whether your ERP is healthy or on fire. From Shopify's perspective the endpoint is permanently fast, so the eight-retry countdown never starts and the subscription is never deleted.

  • Your origin's slowness is decoupled from Shopify's clock. We retry against your server on a schedule measured in hours, not four
  • Every event is persisted before delivery is attempted, encrypted at rest, so a gap is replayable from our log rather than reconstructable from the Admin API
  • Failure-streak alerts at 1, 5, and 20 consecutive failures, sent to an address you control, with auto-pause at 20
  • One-click replay over a time range for the window your server was down. Replays don't consume quota

Shopify's five-second rule is a statement about the endpoint it talks to. Make that endpoint something that is always fast, and the rule stops constraining your business logic.

Takeaway

Shopify's five seconds is not a soft target, and the penalty for missing it repeatedly is deletion rather than a paused endpoint you can spot in a dashboard. Get the response path down to verify-persist-acknowledge, dedupe on X-Shopify-Webhook-Id, and go check what address your Partner account's emergency developer email points at. That is where the only warning you get is going to land.

All postsAugust 3, 2026 · 8 min

Stop losing webhooks.

Change one URL. Get retries, event log, and one-click replay.