A Shopify store generates events all day — an order is placed, a payment captures, a
customer edits their address, a merchant uninstalls your app mid-checkout. Shopify
delivers these as webhooks, and for most apps they are the only reliable signal that
something happened in a store you do not control. Miss an orders/paid and you never
fulfil; miss an app/uninstalled and you keep billing a merchant who left. This
guide covers consuming Shopify webhooks correctly: verifying them, routing on topic,
and surviving the failure handling Shopify enforces on apps.
What Shopify webhooks are
A Shopify webhook is an HTTP POST sent when a subscribed event occurs in a store.
The event topic — Shopify’s term for the type — arrives in the X-Shopify-Topic header (orders/create, orders/paid, app/uninstalled), the originating store in X-Shopify-Shop-Domain, a unique delivery id in X-Shopify-Webhook-Id, and the
resource as a JSON body. A single endpoint typically receives several topics, so you
route on the header.
Common use cases
- Order fulfilment — react to
orders/createandorders/paidto reserve stock, charge, or hand off to a fulfilment system. - App lifecycle — handle
app/uninstalledto stop billing, revoke tokens, and clean up store data promptly. - Inventory and product sync — keep an external catalogue aligned from
products/updateand inventory-level events. - Compliance — Shopify requires apps to handle mandatory data-subject topics (customer data requests and redaction); consult current docs for the exact set.
Configuring the webhook in Shopify
Webhooks are created per topic, either in the Shopify admin (Settings → Notifications, for a store’s own webhooks) or, for apps, via the Admin API or the app configuration. At a high level you choose the topic, the delivery URL, and the format (JSON), and Shopify signs deliveries using a shared secret — your app’s API secret for app-created webhooks. Where and how you register depends on whether you are a store owner or an app developer.
Because the exact registration paths and the mandatory-topic requirements change, confirm current specifics against Shopify’s webhook documentation rather than assuming a fixed set of screens. Do not hard-code topic lists you have not re-checked.
Recommended endpoint setup
Shopify expects a fast response and treats slow or failing endpoints harshly (see below), so acknowledge first and process asynchronously:
- Capture the raw request body — you need the exact bytes for the HMAC.
- Verify
X-Shopify-Hmac-Sha256. - Read
X-Shopify-TopicandX-Shopify-Shop-Domainto route. - Enqueue the work keyed by
X-Shopify-Webhook-Id, and return2xx. - Fulfil, sync, or clean up off the request path.
Filtering by topic early keeps a busy store’s noise out of your handler. A HookWatch endpoint’s event-type allowlist can drop topics you do not act on before they reach your service.
Verifying authenticity
Shopify signs each delivery with the X-Shopify-Hmac-Sha256 header: the base64-encoded HMAC-SHA256 of the raw request body, keyed by your app’s shared
secret.
X-Shopify-Hmac-Sha256: kR9vC1p6Vf8nQ2mX0aZ7bY4uT3sW6eH8jL5oP1dG9c= To verify, compute HMAC-SHA256(shared_secret, raw_body), base64-encode the digest,
and compare it to the header in constant time. Note the encoding: Shopify uses base64, not the hex encoding Stripe and GitHub use — a verifier written for one
provider will fail on the other if the encoding is not switched. And, as always, hash
the raw bytes, not a re-serialised body. The general treatment is in how to verify webhook signatures.
HookWatch’s custom inbound signature scheme covers this directly: header name X-Shopify-Hmac-Sha256, algorithm SHA-256, encoding base64, no prefix. With that
configured, deliveries with a bad HMAC are rejected before your handler runs.
Common failure scenarios
- Base64/hex encoding mismatch — reusing a hex verifier from another provider rejects every valid Shopify delivery.
- Signature failures from a parsed body — a JSON middleware ahead of your verifier changes the bytes and breaks the HMAC.
- Subscriptions removed after sustained failures — Shopify may remove a webhook subscription after an endpoint fails repeatedly over a period; the deliveries then simply stop. Phrase your monitoring around detecting this and re-registering, and consult current Shopify docs for the exact thresholds, which change.
- Missed
app/uninstalled— if this fails and is not recovered, you keep state and billing for a store that is gone. - Duplicate fulfilment — a retried delivery that already succeeded fulfils an order twice without dedupe.
Testing workflow
Shopify offers ways to send a test delivery for a topic from the admin, and app
developers can trigger events by performing real actions in a development store —
placing a test order to fire orders/create, for example. Because the available test
tooling and its location move around, check Shopify’s current docs for what is
available to you.
For local development, expose your handler with a tunnel and register a webhook (or a
development-store webhook) against it so real Shopify deliveries reach your machine
over public HTTPS. See testing webhooks locally; hookwatch tunnel forwards captured deliveries to a local process so Shopify can
reach your laptop through a HookWatch endpoint.
Retry behaviour
Shopify retries failed deliveries for a limited window and then — unlike most providers — may remove the subscription entirely if failures persist. The exact retry schedule and removal threshold change, so rely on the shape of the behaviour and confirm current numbers in the docs. For your handler this means:
- A
2xxis the only success signal; anything else counts as a failed attempt. - Persistent failure is not merely delayed delivery — it can end delivery. Alert on runs of failures rather than treating them as transient.
- Retries and re-registration produce duplicates, so plan for at-least-once delivery.
The broader picture is in designing retry behaviour.
Idempotency
Dedupe on X-Shopify-Webhook-Id, which identifies a delivery and repeats across
retries of that delivery. Record processed ids and make the effect idempotent —
fulfilling one order twice, or double-charging on a duplicate orders/paid, is
exactly the failure dedupe prevents. Because a store can resend and Shopify can
retry, treat every topic as at-least-once. A duplicate you have already handled
should still return 2xx. Patterns and storage choices are in idempotent webhook processing.
Debugging checklist
- Confirm the topic is still subscribed for the store and has not been removed after failures.
- Check the delivery’s recorded response — status, or a timeout — for the failed attempt.
- Confirm the handler reads the raw body before any parser runs.
- Verify the HMAC recomputes and is compared as base64, not hex.
- Confirm the shared secret matches the app that created the webhook.
- Route on
X-Shopify-TopicandX-Shopify-Shop-Domain, not payload guesses. - Ensure the handler returns
2xxfast, with fulfilment deferred. - Match logs to the delivery by
X-Shopify-Webhook-Id. - Confirm duplicate deliveries are deduped and still answered
2xx.
How HookWatch helps
Point a HookWatch endpoint at Shopify and every delivery is recorded whole — the X-Shopify-Topic, X-Shopify-Hmac-Sha256, and X-Shopify-Webhook-Id headers, the
body, your response, and each attempt — so a failed orders/paid still shows the
exact order payload and your handler’s error when you look later. When a run of
failures threatens your subscription, the captured deliveries let you replay the
exact payloads (individually or in bulk) once you have fixed the handler, rather than
losing the orders. Pause an endpoint during a deploy and resume it, filter to the
topics you act on, and let repeated failures group into an incident so a Shopify
problem is one alert with the evidence attached, not a silent gap in your orders.