All articles
Guide

Shopify webhooks: how delivery works and why events go missing

How Shopify delivers webhooks (subscriptions, HMAC, the 5-second timeout, 8 retries in 4 hours), why deliveries go missing and how to handle them reliably.

By Matthias Gayaud, Founding Engineer at Meridian·Updated

Shopify webhooks tell your app when something changes in a store, such as a new order. Your app subscribes to topics like orders/create, and Shopify sends each event to an HTTPS endpoint, a Google Cloud Pub/Sub topic or an Amazon EventBridge bus. Over HTTPS, your endpoint must answer with a 2xx within 5 seconds. Otherwise the delivery fails and Shopify retries it.

In short. Shopify doesn’t promise to deliver every webhook, so plan for gaps. Declare subscriptions in shopify.app.toml, where failing deliveries don’t get them deleted. Answer within 5 seconds and do the work from a queue. Skip duplicates by their X-Shopify-Webhook-Id, and run a reconciliation job that fetches what changed from the API.

How does Shopify deliver webhooks?

A subscription pairs one or more topics with a destination. A topic is a resource and an action, such as products/update or app/uninstalled, and each one needs the access scope that goes with it. When a qualifying change happens in a store, Shopify sends the payload, by default the full REST resource, with headers that describe the delivery.

Shopify recommends using Google Pub/Sub as a cloud-based solution for delivering webhooks. You can also use Amazon EventBridge. If you prefer to build your own webhooks infrastructure, you can deliver through HTTPS, but there are extra considerations to take into account.
Shopify, Manage webhook subscriptions

Shopify has two kinds of subscription. They differ in where you declare them, which shops they cover and what happens when deliveries fail.

App-specificShop-specific
Declared inshopify.app.tomlThe GraphQL Admin API (webhookSubscriptionCreate), often from your app’s afterAuth hook
Applies toEvery shop that installs the appOne shop at a time
Compliance topicsSupportedNot through the Admin API
After repeated failuresKeptDeleted by Shopify
When to useBy default, as Shopify recommendsTopics, destinations or filters differ between shops

Apps on the Shopify App Store must also subscribe to the three mandatory compliance topics, customers/data_request, customers/redact and shop/redact. The guide to Shopify’s protected customer data requirements covers what they ask of your app.

On HTTPS, each delivery carries these headers. On Pub/Sub and EventBridge, the same metadata comes inside the message.

HeaderWhat it carries
X-Shopify-TopicThe topic, such as orders/create
X-Shopify-Hmac-Sha256The base64 HMAC signature, on HTTPS only
X-Shopify-Shop-DomainThe store's myshopify.com domain
X-Shopify-API-VersionThe API version that serialized the payload
X-Shopify-Webhook-IdIdentifies the delivery, to skip duplicates
X-Shopify-Triggered-AtWhen Shopify triggered the webhook
X-Shopify-Event-IdShared by every delivery that comes from the same merchant action

Shopify also offers Events, a newer subscription mechanism with field-level triggers and custom GraphQL payloads, for a subset of topics. Webhooks remain supported, and the two can run side by side in the same shopify.app.toml.

Timeouts, signatures and retries

Over HTTPS, your endpoint has 1 second to accept the connection and 5 seconds to answer the whole request. Shopify verifies your SSL certificate, and any answer outside the 2xx range counts as a failure, 3xx redirects included.

Each delivery is signed. X-Shopify-Hmac-Sha256 holds a base64 HMAC-SHA256 of the raw request body, keyed with your app’s client secret. Your app computes the same value before it parses the body, and rejects any delivery where the two don’t match. Shopify’s React Router app template does this for you in authenticate.webhook.

When a delivery fails, Shopify retries it 8 times over the next 4 hours, waiting longer before each try. After 8 consecutive failures, it deletes the subscription if your app created it through the Admin API, and emails the app’s emergency developer address.

The same webhook can arrive twice, after a timeout or a retry. Order isn’t guaranteed either, within a topic or across topics for the same resource.

Your app shouldn’t rely on receiving data from Shopify webhooks. Webhook delivery isn’t always guaranteed, and your app can miss or mishandle events for other reasons, such as handler failures or downtime.
Shopify, About webhooks

Why do Shopify webhooks go missing?

A missing webhook was refused by your endpoint, had no subscription on that shop, or was dropped by Shopify. A late delivery looks missing too.

CauseWhat happensFix
A slow handlerYour app does the work before it answers and goes past 5 seconds. The delivery fails and waits for a retry.Answer first and do the work afterwards, from a queue.
A redirectShopify counts any 3xx as a failure, such as a redirect from HTTP to HTTPS, to a URL with a trailing slash, or to a login page.Subscribe the exact URL that answers.
A failed HMAC checkYour app parsed the body before checking the signature, or holds the wrong client secret, and answers 401 to real deliveries.Verify the raw body with the current secret. After a rotation, Shopify can take up to an hour to sign with the new one.
A deleted subscriptionAfter 8 consecutive failures, Shopify deletes a subscription your app created through the Admin API. That shop gets nothing more on the topic.Fix the endpoint, then create the subscription again.
No subscription on that shopA shop-specific subscription exists only where your code created it. A topic added to the code later is missing on shops that installed before. Each topic also needs its access scope.List each shop's subscriptions and create the missing ones.
A quick reinstallA store that reinstalls within about a minute of uninstalling finds the previous install's subscriptions still in place, and Shopify deletes them shortly after.Register the store's topics again a few minutes after the uninstall.
A debounced payloadWith include_fields, two events can produce the same payload. Shopify drops the later one when they arrive close together.Include a field that always changes, such as updated_at.
Late or out of orderOrder isn’t guaranteed, so products/update can arrive before products/create, and deliveries can be delayed.Compare X-Shopify-Triggered-At or updated_at before you write, so an older event never overwrites a newer one.

To find which one you have, open your app’s Logs in the Dev Dashboard. They list each delivery and your app’s response for the past 7 days, up to several minutes behind. A response code points at your handler. No delivery at all points at the subscription or its filter.

Handling webhooks reliably

  • Declare your subscriptions in shopify.app.toml, unless their topics, destinations or filters must differ between shops.
  • Or deliver to Pub/Sub or EventBridge. The 5-second timeout and the HMAC check then no longer apply to your endpoint, and you run the consumer that reads from the bus instead.
  • Put the work on a queue and answer as soon as the signature checks out, so a slow database or a call back to Shopify never pushes you past 5 seconds.
  • Make each handler idempotent. Where it can’t be, store every X-Shopify-Webhook-Id and skip the ones you have already processed.
  • Run a reconciliation job that fetches what changed since its last run, with the updated_at filters many GraphQL queries accept. It recovers what the webhooks missed, including after an outage.
  • Watch the delivery metrics on your app’s Overview in the Dev Dashboard. Shopify flags a failure rate above 0.5% as higher than average, and response times of 4 to 5 seconds as a timeout risk.

Good to know. shopify app webhook trigger sends a sample delivery of any topic to any of the three kinds of destination. It goes straight to the address you give it, so it tests your handler, not your subscription. Run it before you change webhooks.api_version, which sets the payload version of every app-specific subscription.

Receiving webhooks through Meridian

Meridian hosts Shopify apps and receives their webhooks over Google Cloud Pub/Sub, then forwards each event to a route in your app, /webhooks/shopify by default. Meridian re-serializes the event, so your app checks Meridian’s signature instead of Shopify’s HMAC. That signature covers the shop, the topic, the webhook ID and the body.

  • Meridian keeps each store’s subscriptions in sync as stores install and uninstall your app. For an app hosted on Meridian, the Webhooks tab shows for each topic whether every store accepted the subscription, with the reason when one didn’t. A store that reinstalls quickly gets its topics registered again, at 2 and at 10 minutes after the uninstall.
  • A live delivery log lists each delivery with its topic, shop, status, attempts and your app’s response code. Your coding agent can read the same routes and deliveries through Meridian MCP.
  • An error other than a timeout, a connection error, a 5xx, 408, 425 or 429 is final after one try. A 404 or a 401 isn’t retried, and the topic’s row on the Webhooks tab shows the status your app returned.
  • An app hosted elsewhere can receive forwarded events too. You declare its domain in Meridian, and your app proves it holds the signing secret by answering a signed check.

For the errors it does retry, Meridian makes two more tries, 10 then 30 seconds apart. That window is far shorter than Shopify’s 4 hours over HTTPS, and a delivery that failed all three tries isn’t sent again. Keep the reconciliation job.

Sources: Shopify’s pages on webhooks, managing subscriptions, the delivery structure, verifying deliveries, troubleshooting and Events and webhooks. Meridian’s Pub/Sub and forwarded webhooks documentation. All checked on 4 November 2026.

Questions.

Why is my Shopify webhook not firing?

Start with the subscription. Does that shop have one for the topic, with the access scope the topic needs and a filter that lets the event through? Then look for the delivery on your app's Logs page in the Dev Dashboard, which keeps 7 days. If it is there, the response code your app returned explains the failure.

How long does Shopify wait for a webhook response?

It allows 1 second to connect and 5 seconds for the whole request. A timeout, a redirect or any status outside 2xx is a failed delivery, and Shopify retries it 8 times over the next 4 hours, waiting longer each time. Keep the handler short: check the signature, queue the event and answer.

Why did Shopify delete my webhook subscription?

Its deliveries failed 8 times in a row, and your app had created it through the GraphQL Admin API. Shopify deletes those and emails the app's emergency developer address. Subscriptions declared in shopify.app.toml are kept. Fix the endpoint, then recreate what is missing with code that lists a shop's existing subscriptions and adds only the absent ones.

Can Shopify send the same webhook twice?

Yes. Shopify keeps duplicates to a minimum, but a timeout or a retry can bring the same webhook back. Write handlers so a second run changes nothing, or keep the X-Shopify-Webhook-Id values you have processed and skip repeats. Events can also arrive out of order, so compare timestamps before overwriting newer data.

Which webhooks must a Shopify App Store app handle?

Every app distributed through the Shopify App Store must subscribe to three compliance topics: customers/data_request, customers/redact and shop/redact. List them under compliance_topics in shopify.app.toml or set them in the Dev Dashboard, because the Admin API can't subscribe to them. They ask your app to send or delete a customer's data, or to delete a store's data.

How does Meridian deliver Shopify webhooks?

Shopify sends your app's webhooks to Google Cloud Pub/Sub, not straight to your app. Meridian forwards each one, signed with your app's own secret. If your app times out or answers a 5xx or 429, Meridian tries twice more, 10 and then 30 seconds apart. Every delivery shows in a live log.

Ready to ship your next Shopify app?

Try Meridian free, or book a personal walkthrough.

14 days, no card needed.