Shopify webhooks connect store activity to your app: orders, products, fulfillment, refunds, inventory, privacy requests, and app lifecycle changes. The difficult bugs usually live between delivery and processing—an incorrect topic, a changed payload version, a modified request body, or a retry that repeats side effects.
This guide covers Shopify's established HTTPS webhook contract with X-Shopify-* headers. CatchHook captures any HTTP request, but provider-specific detection and replay behavior described here applies to that contract.
Create a Shopify webhook endpoint
Open Shopify Webhook Testing and copy the temporary HTTPS endpoint, or create a permanent endpoint in your CatchHook account.
Register that URL as the callback URI for the topics your app needs. Shopify supports app-specific subscription flows through app configuration and the Admin API; follow Shopify's current webhook subscription documentation for the flow that matches your app.
Start with a narrow set of topics:
orders/createfor order ingestionproducts/updatefor catalog synchronizationfulfillments/createfor shipping workflowsrefunds/createfor reconciliationapp/uninstalledfor access revocation and cleanup
Trigger each topic from a development store and confirm the delivery appears before building more automation.
Read the delivery context
A classic Shopify delivery includes useful context in headers:
X-Shopify-Topic: orders/create
X-Shopify-Shop-Domain: example-store.myshopify.com
X-Shopify-Webhook-Id: 7c8d573d-1575-4c09-9ac5-2a1f8467c082
X-Shopify-Event-Id: 2fd4a9ae-68d2-4d1c-9e64-b4ddff112c89
X-Shopify-Api-Version: 2026-07
X-Shopify-Triggered-At: 2026-07-24T18:30:00Z
CatchHook normalizes those bounded fields so the topic, shop, delivery identity, event identity, API version, and trigger time are visible without searching the raw request.
X-Shopify-Webhook-Id identifies a delivery. X-Shopify-Event-Id correlates deliveries caused by the same merchant action. The id inside an order, product, fulfillment, or refund payload identifies the commerce resource—it is not a webhook delivery ID.
Verify the HMAC from the raw body
Shopify sends X-Shopify-Hmac-Sha256, computed from the exact request body using your app client secret. Verification must happen before middleware parses, reformats, or re-encodes that body.
raw_body = request.raw_post
expected = Base64.strict_encode64(
OpenSSL::HMAC.digest("sha256", ENV.fetch("SHOPIFY_CLIENT_SECRET"), raw_body)
)
verified = ActiveSupport::SecurityUtils.secure_compare(
expected,
request.headers["X-Shopify-Hmac-Sha256"].to_s
)
On a saved CatchHook endpoint, add a Shopify signature configuration with the app client secret. CatchHook verifies the raw delivery and reports verified, failed, missing, or not configured. During secret rotation, the current and previous encrypted secrets can both verify deliveries until the rotation is complete.
See Webhook Signature Verification for framework-specific failure patterns.
Treat API versions as debugging context
X-Shopify-Api-Version identifies the API version used for the webhook payload. When test and production payloads differ, compare this header before assuming your parser is broken.
Use CatchHook's request comparison to put a working and failing delivery side by side. Shopify-aware diff context highlights normalized topic, shop, API version, delivery identity, resource type, and resource ID while the generic diff still shows body and header changes.
Make handlers idempotent
Shopify can retry deliveries, and your own replay can invoke the handler again. Record an idempotency key before performing state-changing work. Choose whether your workflow deduplicates each delivery or the underlying merchant event based on the behavior you need.
delivery_id = request.headers["X-Shopify-Webhook-Id"]
return head :ok if ProcessedShopifyDelivery.exists?(delivery_id: delivery_id)
ProcessedShopifyDelivery.create!(delivery_id: delivery_id)
process_shopify_event(JSON.parse(request.raw_post))
head :ok
Order, fulfillment, refund, inventory, and uninstall handlers deserve extra care because a duplicate can repeat fulfillment, financial, stock, or cleanup work. Read Webhook Retries and Idempotency before replaying those topics.
Shopify does not guarantee ordering within one topic or across topics for the same resource. Treat X-Shopify-Triggered-At as troubleshooting context, not as an ordering lock. For workflows that cannot tolerate a missed or out-of-order update, reconcile against Shopify's API after processing the webhook.
Replay without changing commerce identities
CatchHook's replay modes are explicit:
- Preserve original signature sends the stored header as captured. A modified body will no longer match that signature.
- Strip signature removes Shopify's HMAC before sending.
- Re-sign with configured secret removes the old HMAC and signs the outgoing body with the active Shopify configuration.
The Shopify New delivery ID option regenerates only X-Shopify-Webhook-Id. It preserves X-Shopify-Event-Id and leaves the entire body unchanged, including order and product IDs.
Deliver Shopify webhooks to localhost
Shopify needs a public HTTPS callback. Use your CatchHook endpoint as that callback, then run the CatchHook tunnel to stream captured requests to your development server:
npx @catchhook/tunnel start --endpoint ep_xxx --port 3000
The tunnel connects your machine to CatchHook and delivers requests to localhost while the hosted history remains available for inspection and retry. See Test Webhooks Locally for the complete workflow.
Automate with Actions and inspect with MCP
Shopify-aware Action conditions can use allowlisted normalized fields such as topic, shop domain, API version, resource type, and delivery ID. Forward Actions target remote destinations; use the tunnel for delivery to your local machine.
The CatchHook MCP server exposes the same provider event type, category, and normalized event data to supported AI editors. That lets an agent inspect a failed delivery with the relevant Shopify context without granting it access to your Shopify admin.
A practical release checklist
- Register only the topics your app handles.
- Verify HMAC against the raw request body.
- Confirm the expected shop domain and API version.
- Store an idempotency key before side effects.
- Test failures and retries through the tunnel.
- Review replay warnings before sending state-changing topics.
- Keep privacy and redaction handlers minimal and auditable.
Use the Shopify webhook testing endpoint to capture a real development-store delivery and verify these assumptions against the bytes Shopify actually sends.