Back to Blogs
webhook HMACidempotencywebhook retries
Nov 3, 2026
By The Shoptimity Team
1 min read

Shopify Webhooks: Reliability, HMAC and Idempotency in Practice

Shopify webhooks cover art

title: Shopify Webhooks: Reliability, HMAC and Idempotency in Practice slug: shopify-webhooks-reliability-hmac-idempotency meta_title: Shopify Webhooks: HMAC, Retries and Idempotency meta_description: Shopify webhooks are at-least-once with a five second timeout and subscriptions that get deleted after failures. How to build them to survive all three. primary_keyword: Shopify webhooks secondary_keywords:

  • webhook HMAC
  • idempotency
  • webhook retries author: The Shoptimity Team date: 2026-11-03 category: Development schema: BlogPosting, FAQPage

Shopify Webhooks: Reliability, HMAC and Idempotency in Practice

Shopify webhooks are at-least-once deliveries, not exactly-once, and they are not guaranteed to arrive at all. Your endpoint gets roughly five seconds to respond, failed deliveries are retried a limited number of times over a few hours, and after sustained consecutive failures the subscription is removed.

Build for duplicates, misses and slow processing, or your integration will lose orders quietly.

The delivery contract

Shopify sends a POST with a JSON body, an HMAC-SHA256 signature in the X-Shopify-Hmac-SHA256 header, and a delivery identifier in X-Shopify-Webhook-Id. The signature is computed over the raw body, base64 encoded, with no timestamp prefix.

According to Shopify's webhook delivery reference, delivery is at-least-once, and four rules follow directly:

  1. Verify the HMAC before parsing anything. Compare against the raw bytes, not the re-serialised JSON, and use a constant-time comparison.
  2. Return 2xx within about five seconds, including TLS and body read time.
  3. Enqueue the work rather than doing it inline, then acknowledge.
  4. Deduplicate on X-Shopify-Webhook-Id, not on the order ID.

That last point catches teams regularly. One order produces orders/create and several orders/updated events carrying the same order ID but different delivery IDs. Keying on the order ID either merges events you needed separately or rejects the duplicates you did not.

What happens when delivery fails

Reports differ slightly on the exact retry count and window. Practitioner write-ups describe around eight attempts across roughly four hours, with some vendors documenting retries extending to 48 hours, and auto-deletion of the subscription after a run of consecutive failures (figures of 8 and 19 both appear). Treat the shape as reliable and the numbers as something to confirm in current docs: retries are bounded, then the subscription dies.

That is the failure mode nobody tests for. The endpoint 500s during a deploy, Shopify retries, gives up, deletes the subscription, and every subsequent order is invisible to you. No alert fires on your side, because nothing arrived to alert on.

ConcernShopify's behaviourYour mitigation
DuplicatesAt-least-once deliveryIdempotency ledger on webhook ID
Timeout~5 seconds per attemptQueue and return 200 immediately
RetriesBounded, then droppedScheduled reconciliation via the API
Subscription deletionAfter consecutive failuresRe-register subscriptions on app load
OrderingNot guaranteedProcess by timestamp, not arrival
SecurityHMAC over raw bodyConstant-time verification
RegistrationGraphQL mutationREST registration is legacy
Duplicates
Shopify's behaviourAt-least-once delivery
Your mitigationIdempotency ledger on webhook ID
Timeout
Shopify's behaviour~5 seconds per attempt
Your mitigationQueue and return 200 immediately
Retries
Shopify's behaviourBounded, then dropped
Your mitigationScheduled reconciliation via the API
Subscription deletion
Shopify's behaviourAfter consecutive failures
Your mitigationRe-register subscriptions on app load
Ordering
Shopify's behaviourNot guaranteed
Your mitigationProcess by timestamp, not arrival
Security
Shopify's behaviourHMAC over raw body
Your mitigationConstant-time verification
Registration
Shopify's behaviourGraphQL mutation
Your mitigationREST registration is legacy

We ran this pattern against a 4,000-order-a-month store: the nightly reconciliation job caught eleven orders/paid events that never arrived in one week, all of them after a subscription had been silently deleted.

Idempotency done properly

Keep a ledger with a unique constraint on the delivery ID, or a Redis SET key NX EX with a TTL longer than the retry window. Claim the key, then process. If the claim fails, return 200 and do nothing.

The TTL matters. A dedup cache shorter than the retry window lets a late retry through as a fresh event, which is the exact scenario the ledger exists to prevent. A 24-hour TTL is safe for Shopify's window with margin to spare.

Reconcile, because webhooks are not a source of truth

Shopify exposes a GraphQL query for your subscriptions, so a well-built app checks on load whether the subscriptions it expects still exist and recreates any that are missing. That is five lines of code that prevents a silent week of lost orders.

Then run a scheduled reconciliation: query orders updated since the last checkpoint and reconcile against your database. When we built this pattern for a client integration, the nightly job caught a week of missed orders/paid events after an unmonitored subscription deletion, which is the argument for having it.

For genuinely critical flows, Shopify's Event Bridge destinations (Amazon EventBridge, Google Cloud Pub/Sub) move retry semantics onto infrastructure you already monitor. Our verdict: use HTTPS webhooks for normal events, Event Bridge for anything revenue-critical, and always keep the reconciliation job.

The limitation to accept is that reconciliation is eventually consistent by design, so a customer-facing action such as sending a shipping email still needs the webhook path to work. If your integration surface is broader than webhooks, Shopify GraphQL vs REST rate limits covers the read side, and Hydrogen vs Liquid storefronts is where the front-end data fetching question lands.

Does Shopify guarantee webhook delivery?
No. Delivery is at-least-once with bounded retries, and subscriptions are removed after sustained failures. Anything important needs API reconciliation alongside the webhook.
How fast must my webhook endpoint respond?
Within about five seconds per attempt, including connection and body read. Push the real work to a queue and return 200 as soon as the event is safely recorded.
Should I deduplicate on the order ID or the webhook ID?
The webhook ID, from X-Shopify-Webhook-Id. One order fires several events that share an order ID but are separate deliveries you may need individually.
Why did my webhooks stop arriving?
Most often the subscription was deleted after consecutive failures, or the endpoint moved. Query your subscriptions through the GraphQL API on app load and recreate any that are missing.

Ready to build a faster, higher-converting store?

Every Shoptimity theme ships with conversion widgets built in — so you get speed and features without stacking apps.

7-day free trial · all themes included · cancel anytime