Shopify counts a delivery as successful only when your endpoint answers with a 2xx within 5 seconds (with a 1-second connection timeout). Every other outcome is a failure, including 3xx redirects. Shopify then retries 8 times over 4 hours. Work through these in order.
A 401 almost always comes from your own code. Your HMAC check rejected the request, or auth middleware blocked it before your handler ran. Common reasons:
If you do the real work (database writes, API calls, emails) before you respond, you'll hit the 5-second limit under load. Return 200 right away and process in a queue or background job.
If 8 retries in a row fail, Shopify automatically deletes subscriptions created through the Admin API and emails warnings to the app's emergency developer address. Webhooks then stop with no error at all. Fix the endpoint, then create the subscription again. Subscriptions declared in shopify.app.toml are managed by Shopify and aren't removed this way.
http to https, apex to www, or trailing-slash redirects all count as failures. Register the exact final https URL. Retries go to the address that was configured when the event fired, so keep the old endpoint running for a while after you migrate.
Check that the subscription exists on the store you're testing (or in the app config) and uses the topic you expect. Use the delivery metrics in the Partner or Dev Dashboard to see response codes and timings for each attempt.
The guide is Stripe-only. The kit is a self-hosted Cloudflare Worker relay that adds retries, replay and a dead-letter list in front of your endpoint. Answering fast and retrying later helps you avoid removed subscriptions. It verifies Shopify's X-Shopify-Hmac-Sha256 out of the box. Set SCHEME_<ID>=shopify. It works the same way for Stripe, Lemon Squeezy, Paddle Billing, Square, Twilio (status callbacks) and GitHub.
Fix Your Stripe Webhooks guide · Self-Hosted Webhook Relay Kit. Code LAUNCH40 = 40% off until Sun Oct 11, 6:40 AM MT.
curl -i -X POST https://your-app.com/webhooks/shopify \
-H 'Content-Type: application/json' -H 'X-Shopify-Topic: orders/create' -d '{}'
# 3xx = a redirect in front of your app
# 401 = your HMAC or auth layer (expected here, since this request isn't signed)
# slow = move the work after the 200
Retried deliveries carry the original payload. Use the X-Shopify-Triggered-At header to spot stale ones, and de-duplicate on the webhook ID.
Free download: Webhook debugging cheat sheet.
Fighting webhooks on more than one platform? The Fix Your Stripe Webhooks guide ($9) covers raw-body and signature problems in depth, and the Self-Hosted Webhook Relay Kit ($19) adds retries, replay and a dead-letter list in front of any endpoint.