Shopify Subscription Webhook Failures: Why They Happen and How to Detect Them
When Shopify fires a subscription webhook — such as subscription_billing_attempts/failure or subscription_contracts/cancel — and your endpoint returns a 5xx error or times out, Shopify retries delivery for up to 48 hours. If the endpoint stays down, the event is permanently lost. The receiving app (Recharge or your custom app) never processes the state change. The result is a cross-stack mismatch: Shopify records the event internally, but Recharge never learns about it. Subscriptions appear active in one platform and cancelled in another. Here is how to detect and fix webhook delivery failures.
What causes Shopify subscription webhook delivery failures?
Webhook delivery fails when your endpoint returns a non-2xx HTTP status or does not respond within Shopify's timeout window. Common causes: deployment downtime during a code push, server errors introduced by a recent change, HMAC validation code that incorrectly rejects valid payloads, rate limiting on your server rejecting Shopify's requests, or misconfigured webhook endpoint URLs after a domain change.
HMAC validation is a frequent silent killer. Shopify includes an X-Shopify-Hmac-Sha256 header with every webhook. If your validation logic has a bug — for example, reading the raw body after a JSON parser has already consumed it — every webhook appears invalid and is dropped. This generates no alerts and creates no errors in Shopify's delivery log, because the webhook was delivered but your code rejected it.
How do you check the Shopify webhook delivery log for failed deliveries?
In Shopify Admin, go to Settings > Notifications and scroll to the Webhooks section. Click on any subscription-related webhook endpoint to view its delivery history. Shopify shows recent delivery attempts with HTTP response codes and timestamps. Any 5xx responses or timeouts indicate your endpoint failed to process the event.
Shopify's webhook log only retains recent delivery history. If failures occurred weeks ago, the log may not show them. For a complete picture, cross-reference the delivery log with your application's own request logs. Look for gaps in event processing — periods where your app shows no webhook activity even though billing cycles were occurring.
What state mismatches result from missed subscription webhooks?
The most common mismatch from missed webhooks is a ghost subscription: Recharge does not receive the subscription_contracts/cancel webhook, so it continues showing the subscription as ACTIVE and may continue billing attempts on a subscription that Shopify has already cancelled. Conversely, a missed subscription_billing_attempts/success webhook means Recharge does not know a payment succeeded, and may trigger dunning emails incorrectly.
Missed subscription_billing_attempts/failure webhooks are particularly damaging. If Recharge does not receive the failure notification, it cannot start the dunning process. The subscription stays in its pre-failure state, no retry is scheduled, and no customer email is sent. Revenue leaks silently until someone manually checks billing attempt status.
How do you make your subscription webhook endpoint resilient to delivery failures?
First, ensure your webhook endpoints are idempotent. Shopify guarantees at-least-once delivery — the same event may be delivered multiple times. Your handler must process duplicates safely, typically by checking whether an event ID has already been processed before taking action. Store processed event IDs in a database with a short TTL.
Second, respond with HTTP 200 immediately and process the webhook asynchronously. If your handler takes longer than Shopify's timeout to respond, Shopify marks the delivery as failed and retries. Return 200 immediately upon receipt, queue the event for processing, and handle it in a background job. AltorLab detects the downstream symptom — state mismatches between Shopify and Recharge — which serves as an indirect alert that webhook delivery has failed.
Frequently asked questions
How long does Shopify retry failed webhook deliveries?
Shopify retries failed webhook deliveries for up to 48 hours using exponential backoff. After 48 hours without a successful delivery, Shopify stops retrying and the event is permanently lost. There is no mechanism to replay lost events from Shopify's side — you must detect the state mismatch and reconcile manually.
What happens to my subscription data if Shopify webhook retries are exhausted?
The state change is never processed by your app. If Recharge missed a subscription_contracts/cancel webhook, it continues showing the subscription as ACTIVE. If it missed a subscription_billing_attempts/failure webhook, dunning never starts. You must detect these mismatches by cross-referencing Shopify and Recharge API state directly.
How do I check if my Shopify webhooks are being delivered successfully?
Go to Shopify Admin > Settings > Notifications > Webhooks. Click your subscription webhook endpoint to view delivery history and HTTP response codes. Also check your application server logs for incoming requests on the webhook path. Any gap between expected and actual deliveries indicates a problem.
What is an idempotent webhook handler and why is it required for Shopify subscriptions?
An idempotent handler produces the same result whether an event is processed once or multiple times. Shopify delivers webhooks at-least-once, meaning duplicates are possible. Without idempotency, processing a duplicate could cancel a subscription twice, trigger two dunning emails, or create duplicate billing attempts.
How do I detect a ghost subscription caused by a missed webhook?
Compare Shopify subscription contract status with Recharge subscription status for the same customer. If Shopify shows CANCELLED but Recharge shows ACTIVE, a webhook was likely missed. AltorLab automates this cross-stack comparison and flags mismatches as ghost subscriptions with estimated revenue impact.