Shopify Webhook Security: Protecting Your Store’s Data Flow
Table of Content
A subscription box business in Hobart asked us to look at why their fulfilment system was occasionally double shipping orders. They had built a custom integration that listened to Shopify order webhooks and pushed each one into their warehouse management system. The bug was subtle: the webhook receiver had no idea who was calling it. When the warehouse provider’s network glitched and Shopify retried a webhook, the integration treated it as a brand new order. When a curious developer figured out the endpoint URL and POSTed a test payload, the system happily created an order in the WMS with no purchase behind it. The double shipping was the visible symptom. The bigger issue was that the integration was wide open.
Webhooks are how Shopify tells your other systems that something happened: a new order, an inventory change, a refund, an app uninstall. They are powerful and they are surprisingly easy to get wrong. This article walks through the security controls that every Shopify webhook integration should have, written for the developers and technical operators who actually build these things.
The shape of a Shopify webhook
Shopify sends webhooks as HTTPS POST requests with a JSON body. Each request includes a set of headers that identify the event, the shop, the API version, and crucially a HMAC SHA256 signature computed over the request body using a secret you share with Shopify. The signature is the foundation of webhook security. Without verifying it, you cannot trust anything about the request.
The key headers to know:
X-Shopify-Topic: the event name (orders/create, refunds/create, app/uninstalled).X-Shopify-Hmac-Sha256: the signature you must verify.X-Shopify-Shop-Domain: the store domain (only trustworthy after HMAC verification).X-Shopify-Webhook-Id: unique identifier you can use for deduplication.X-Shopify-Triggered-At: timestamp from Shopify, useful for replay protection.
HMAC verification: the non negotiable
Compute the HMAC SHA256 of the raw request body using the webhook secret, base64 encode it, and compare it to the header value using a constant time comparison. Three details that catch teams out:
- Use the raw body: any reformatting (re serialising JSON, stripping whitespace) will break the signature. Read the body before any middleware that parses it.
- Use constant time comparison: a naive string equals can leak the signature through timing attacks. Use
hmac.compare_digestin Python,crypto.timingSafeEqualin Node, or the equivalent in your language. - Reject mismatches with 401: do not return 200 to invalid requests, or attackers can probe your endpoint without leaving footprints.
If verification fails, log enough to investigate (source IP, topic, timestamp) but never log the signature itself.
Replay protection
HMAC verification proves that whoever sent the request had your secret. It does not prove the request is recent. An attacker who recorded a valid webhook (through a logging mishap, a server breach, or a proxy compromise) can replay it as many times as they like.
Defend against replay with two techniques:
- Timestamp check: reject webhooks where
X-Shopify-Triggered-Atis more than a few minutes old. Shopify retries webhooks for up to 48 hours, so allow a reasonable window, but not unlimited. - Idempotency by webhook ID: track every
X-Shopify-Webhook-Idyou have processed in a fast key value store (Redis, DynamoDB) and skip duplicates. Combined with the timestamp check, this defeats replay completely.
The Hobart subscription business’s double shipping was solved with exactly this idempotency layer. Shopify’s legitimate retries stopped causing duplicate shipments, and the curious developer’s test POSTs were rejected because they had no valid HMAC.
Secret rotation
Webhook secrets, like any credential, should rotate periodically and immediately when there is a reason to believe they are compromised. Reasons to rotate:
- A developer with access to the secret leaves the team.
- The secret was logged or committed to a repository.
- An infrastructure breach is suspected.
- An app you used to receive webhooks is decommissioned.
- Annual rotation as a general practice.
Shopify supports rotating webhook signing secrets through the admin or API. Plan the rotation: support both old and new secrets for a brief overlap window so in flight webhooks succeed, then remove the old secret.
Handling retries gracefully
Shopify retries failed webhooks with exponential back off for up to 48 hours. Your endpoint must:
- Respond quickly. Aim for under 5 seconds. If you need more processing time, return 200 immediately and process asynchronously.
- Be idempotent. Receiving the same webhook twice should produce the same end state, not two of something.
- Return 5xx only for transient failures you want retried. Return 401 for invalid signatures (Shopify will not retry these). Return 200 once you have accepted the webhook for processing.
If your endpoint is consistently slow or returns errors, Shopify may eventually disable the webhook subscription. Monitor your endpoint health and alert on degradation.
Dead letter queues
For webhooks that pass verification but fail business logic (a referenced product no longer exists, a downstream system is down, a payload is malformed in an unexpected way), do not silently drop them. Route them to a dead letter queue where they can be reviewed and replayed.
SQS dead letter queues, RabbitMQ DLX, or a simple database table all work. The point is to have a place where stuck messages accumulate visibly, so you can find and fix the cause.
Monitoring and alerting
Webhook integrations fail quietly by default. Build observability into yours.
- Count of webhooks received per topic per hour. Sudden zero or sudden spike both indicate problems.
- Verification failure rate. Should be near zero. A jump means either a misconfiguration or a probing attacker.
- Processing latency. Slow processing means future retries are likely.
- Dead letter queue depth. Should drain regularly.
- End to end success rate compared to the orders or events the webhook represents.
Put these on a dashboard. Alert when they move outside normal ranges. The cost of monitoring is small compared to the cost of discovering, three weeks late, that you missed a thousand order webhooks.
Common mistakes we find
- Skipping HMAC verification entirely: still depressingly common in internal integrations.
- Using a parsed body for HMAC: most frameworks parse JSON before your handler sees it. Capture the raw body explicitly.
- String equals comparison for the signature: timing attack waiting to happen.
- No replay protection: signature passes, request executes, attacker repeats.
- No idempotency: legitimate retries from Shopify create duplicates.
- Endpoint discoverable from public app metadata: combined with no verification, this is a self serve abuse vector.
- Webhook secret in a repo: secret managers exist for a reason.
App uninstall webhooks: the special case
If you build a Shopify app, the app/uninstalled webhook is your only reliable signal that a merchant has removed you. After uninstall, your access tokens are revoked, so any periodic polling will start failing silently. Treat the uninstall webhook as a first class event: verify it like any other, then trigger your cleanup pipeline. Stop scheduled jobs, mark the merchant as inactive, and start the data retention clock if your privacy policy commits to deleting their data after a period. Forgetting this step leaves you holding personal information from merchants who have moved on, which is awkward when the regulator asks why.
A reference checklist for your endpoint
- HMAC SHA256 verification using constant time comparison against the raw body.
- Timestamp check rejecting requests older than your chosen window.
- Idempotency check using
X-Shopify-Webhook-Id. - Quick acknowledgement (under 5 seconds) with async processing for heavy work.
- Correct status codes: 401 for invalid signature, 200 for accepted, 5xx for transient failure.
- Dead letter queue for poison messages.
- Monitoring on receipt rate, failure rate, latency and DLQ depth.
- Secrets in a secret manager with documented rotation cadence.
- Logs that capture enough to investigate without leaking secrets.
Where Defyn fits in
Webhook security is part of a healthy integration stack and an area where small bugs cause expensive symptoms. We help Australian Shopify merchants and their development teams design webhook receivers that are correct under retry, replay and adversarial conditions. The work often pairs with broader infrastructure improvements like Sydney based hosting and edge protection. Take a look at our web development services or start a project if you want a webhook security review. Our audit and support retainer includes periodic integration reviews so quiet failures get caught before they become loud ones.
Building or hardening a webhook integration and want it done right first time? Our Shopify development team designs secure, retry-proof integrations for Australian merchants.
