Shopify auto-deleted my webhook subscription — what now?
Shopify removed a webhook subscription on you. Eight failed deliveries inside 4 hours did it. The warning email went to an inbox nobody reads. Here's the detection, the re-registration, and the Admin API backfill to recover the events you lost in the gap.
Last reviewed · Verified against Shopify dev docs.
Why did Shopify auto-delete my webhook subscription?
webhookSubscriptionCreate; backfill the gap from the Admin API. There's no replay of dropped events.
What actually triggers the removal
From the troubleshooting doc:
Shopify retries failed webhook calls up to eight times in a four-hour period, but if failures persist past that point the webhook subscription is removed.
And from the HTTPS subscribe doc:
the webhook subscription is automatically deleted if it was configured using the Admin API.
Two conditions, both required. Eight failed attempts inside the 4-hour retry window, plus the subscription must have been created through the Admin API. Subscriptions declared in your shopify.app.toml or registered through the Partner Dashboard behave a bit differently — they're tied to the app config rather than to a database row Shopify can quietly drop. The Admin-API case is the common one and the dangerous one.
"Failed attempt" covers more than a 5xx. Shopify counts any of these as failures:
- Non-2xx response (3xx, 4xx, 5xx)
- Response took longer than 5 seconds
- TLS handshake didn't complete
- DNS resolution failed
- Connection reset / closed before response
The warning email almost nobody reads
Shopify sends a warning to the Partner emergency developer email before the subscription is removed. Two facts about that email that make it ineffective:
- It goes to the Partner account email. That's set when you created the Partner account. For most teams it's the founder's personal inbox, the CTO's email, or a shared
partners@alias nobody monitors. It's almost never the inbox where your on-call engineer lives. - It doesn't include the payload. Even if you read it, you can't resurrect the event from the email. The remediation it points you to is "fix your endpoint and re-register," not "click here to replay."
Two ways to fix this:
- Set up a forwarding rule from the Partner email to whatever channel your on-call actually watches. Slack, PagerDuty, an alerting webhook.
- Don't rely on Shopify's email at all — run a daily cron that queries
webhookSubscriptionsand alerts if any expected topic is missing.
Detecting removal early
The cheapest detection is the daily cron. Query the live subscriptions, diff against the topics you expect to be registered, alert on the diff:
query {
webhookSubscriptions(first: 100) {
edges {
node {
id
topic
endpoint {
... on WebhookHttpEndpoint { callbackUrl }
}
}
}
}
}
Run this once an hour for high-stakes topics (orders, fulfillments). Daily is enough for the long tail. Compare the result against a static list in your code of what should be there.
A second, less obvious signal: silence in your handler logs. If orders/create usually fires N times a day and suddenly it's zero for three hours, something is off. Could be a slow business day. Could be your subscription is gone. Threshold-based alerting on per-topic event count is noisy but catches the slow degradation cases the cron doesn't see between runs.
Recovery flow
Step 1: confirm removal
Run the GraphQL query above. If the topic is missing, the subscription is gone. If the topic is present but the callbackUrl is wrong, that's a different problem — somebody (or some deploy script) overwrote the URL.
Step 2: re-register
mutation {
webhookSubscriptionCreate(
topic: ORDERS_CREATE
webhookSubscription: {
callbackUrl: "https://yourapp.com/webhooks/shopify",
format: JSON
}
) {
webhookSubscription { id topic }
userErrors { field message }
}
}
Watch for userErrors. The common ones: callback URL didn't pass Shopify's verification ping (returns a 200 within 5s? has valid TLS?), missing required scopes for the topic, or you hit the per-app subscription limit.
Step 3: backfill the gap
This is the part most teams skip and regret later. The interval from "first failure in the retry sequence" to "subscription re-registered" is a black hole — events fired during it are gone from Shopify's queue.
The reconstruction path is Admin API queries scoped to the gap window:
mutation {
bulkOperationRunQuery(
query: """
{
orders(query: "updated_at:>='2026-05-11T08:00:00Z' updated_at:<'2026-05-11T13:00:00Z'") {
edges { node { id legacyResourceId createdAt updatedAt } }
}
}
"""
) {
bulkOperation { id status }
userErrors { field message }
}
}
Bulk operations run in the background, complete on their own schedule (up to ~10 days), and don't count against the standard rate limit. Poll currentBulkOperation until status is COMPLETED, download the JSONL result, diff against what's in your local store, and synthesize webhook-shaped payloads for the gaps.
If your handler verifies HMAC, you'll need to re-sign synthesized payloads with your client secret. Otherwise the replay fails verification and goes straight to your DLQ.
Preventing removal in the first place
The root cause is always the same: 8 deliveries failed inside the retry window. Fixes ranked by impact:
- Ack 200 immediately, process async. If your handler is reading and writing in the request lifecycle, every slow query is a potential timeout. Move the work to a queue, return 200 in under 100ms, never timeout.
- Don't make webhook delivery health depend on third-party APIs. A handler that calls an upstream ERP / inventory service synchronously fails every time that upstream is slow. Queue the inbound, then call the upstream from a job that can retry independently.
- Monitor your endpoint's 200-rate from outside. Shopify is essentially a synthetic monitor with a strict 5s SLA. Pingdom / Datadog / a curl in cron lets you find out before Shopify does.
- Have a retry buffer. Whatever queue you use (Sidekiq, BullMQ, SQS), set per-job retry to at least cover the 4-hour Shopify window plus enough headroom that your retry backoff doesn't run out mid-incident.
Where HookRescue fits
We re-register removed subscriptions automatically. The check runs hourly against the Admin API: if a topic we expect to see is gone, we re-create it with the same callback URL and log the event. We also extend the retry curve to 7 days, which means transient outages rarely make it to the 8-failure threshold in the first place. And hourly Admin API reconciliation backfills the gap between removal and re-register so the dropped events don't stay dropped.
Related problems
- Shopify webhook not firing: debug checklist and recovery steps
- Shopify webhook subscription removed: causes, detection, recovery
- How to recover missed Shopify orders after a webhook failure
- Shopify webhook retry policy: 8 retries, 4 hours, then dropped
- Webhook monitoring for Shopify apps: what to track and why
- Did Shopify change the webhook retry policy?
- Shopify webhook deduplication with X-Shopify-Event-Id