Skip to content
newThe webhook.co MCP server is live — turn any webhook into an agent event. Read the docs
test locally

Test Shopify webhooks locally

Five seconds is the whole delivery budget, and Shopify's supply of second chances runs out. A capture URL in front of your handler decouples the two.

The loop

Create an endpoint and it prints a permanent ingest URL. Everything sent there is captured whole — headers, body, the exact bytes — before anything else happens. Stream those events to a port on your machine, and replay any one of them as often as you like while you fix the handler. Forwarding runs from your own machine, so it isn't a billable delivery.

wbhkshopify
$ wbhk endpoints create shopify-dev
https://wbhk.my/whep_…
$ wbhk listen <endpoint-id> --forward http://localhost:3000/webhooks
$ wbhk replay <event-id> --forward http://localhost:3000/webhooks

What's different about Shopify

Shopify's answer to a failing endpoint doesn't stop at the failed delivery. After eight consecutive failures the subscription itself is removed, for subscriptions created through the Admin API — that's what Shopify documents, not an edge case. The delivery budget is one second to connect and five seconds for the entire request, and any status outside the 200 range counts as an error, 3XX redirects included. Put a tunnel in front of your laptop and a debugger breakpoint doesn't only cost you one event — it can quietly unregister the thing you were testing. Acknowledge from a capture URL and replay at your own pace.

Where the URL goes

Store owners create subscriptions in the Shopify admin under Settings, then Notifications, then Webhooks, where Create webhook asks for the event, JSON or XML, the destination URL, and a webhook API version. The event can't be changed after creation, so a wrong pick means a fresh subscription. App developers instead put the ingest URL in the uri key of a webhooks.subscriptions block in shopify.app.toml, alongside a topics array holding the events they want, or create a shop-specific subscription with the webhookSubscriptionCreate mutation on the GraphQL Admin API.

Every saved webhook in the admin carries a Send test option behind the ... overflow menu on its row, which fires a sample notification at the URL you registered. App builds get shopify app webhook trigger, which delivers a sample Admin API topic payload to an address you name, localhost ports included. Two caveats straight from Shopify's CLI reference: the triggered payload never varies, and triggered webhooks aren't retried when they fail.

What Shopify sends

Topics are lowercase resource/action pairs split by a slash — noun first, verb second. The GraphQL Admin API publishes the same set as SCREAMING_SNAKE_CASE enums, so ORDERS_CREATE and orders/create are one topic in two dialects. Mind the spelling: Shopify writes cancelled with two Ls.

  • orders/create
  • orders/paid
  • orders/updated
  • orders/cancelled
  • products/update
  • app/uninstalled

Worth knowing first

Five seconds, then it's an error

One second to open the connection, five seconds for the whole request. Anything outside the 200 range is failure, and that explicitly includes 3XX — so a trailing-slash redirect on your route is scored as a failed delivery rather than a hop Shopify takes. Acknowledge with 200 OK first, do the work afterwards.

Failed deliveries can unregister you

A failure earns eight retries, spread over four hours per Shopify's HTTPS delivery page, and eight consecutive failures delete an Admin-API-created subscription. Shopify's own troubleshooting page words the removal window as a 24-hour period instead, so treat the exact figure as unsettled and the outcome as real.

Duplicates arrive, order doesn't hold

Shopify doesn't promise ordering within a topic or across topics touching the same resource, and says outright that delivery isn't assured. Deduplicate on X-Shopify-Webhook-Id, sequence with X-Shopify-Triggered-At or the payload's updated_at, and keep a reconciliation job that reads the API for whatever never turned up.

localhost isn't a deliverable destination

Shopify won't deliver webhooks to localhost, to any URL ending in the word internal, to fake domains, to Shopify domains, or to a custom domain attached to the store. Deliveries go out to an https:// URI with SSL certificates verified on delivery, so the destination has to be publicly reachable and terminate TLS properly.

Signature checking is handled for you once the secret is registered — you can also check a captured signature by hand in the signature verifier.

Shopify webhooks: common questions

Why did Shopify delete my webhook subscription while I was testing?

Consecutive failed deliveries remove a subscription that was created through the Admin API — see the delivery-window gotcha above for how few it takes and why Shopify's own pages disagree on the window. An endpoint that was stopped at a breakpoint, slow, or answering with a redirect is enough to get there. Keeping a capture URL in front of your machine absorbs those failures instead.

Does the Shopify CLI webhook trigger command replace real store events?

Not for anything that branches on payload contents. Shopify documents that webhooks triggered this way always carry the same sample payload, and that they are not retried when they fail. Use it to prove routing and signature handling, then drive a real order through the store.

Which header does Shopify sign webhook deliveries with?

Shopify's docs render it as X-Shopify-Hmac-SHA256. There is no subscribe-time challenge or echo to answer, so verification is always after the fact, per delivery, rather than something you prove once at registration.

Point a webhook at it. Watch it land.

Start free: a permanent URL, full inspection, one-command replay — and outbound delivery, because every feature is on every plan. Move up when you need more events.