To verify a Shopify webhook, compute an HMAC-SHA256 of the raw request body using your app's client secret, then compare it in constant time with the base64 value in the X-Shopify-Hmac-SHA256 header. Reject any mismatch, answer quickly, and deduplicate with the webhook ID.
The verification steps
From Shopify's webhook documentation:
- Read the
X-Shopify-Hmac-SHA256header, which carries the signature, base64 encoded. - Take the raw request body. Capture it before any body parser middleware runs, and put your verification ahead of body parsing. A parsed and re-serialized body will not match.
- Compute HMAC-SHA256 over that body with your app's client secret.
- Decode the header value and compare it to your digest using a constant time comparison. The Shopify example uses
crypto.timingSafeEqual. - Reject mismatches.
const digest = crypto.createHmac('sha256', secret).update(rawBody).digest();
const received = Buffer.from(headerValue, 'base64');
const ok = received.length === digest.length && crypto.timingSafeEqual(digest, received);
The length check matters because timingSafeEqual throws on buffers of different length.
Where it applies
HMAC applies only to HTTPS deliveries. The docs note that Google Cloud Pub/Sub and Amazon EventBridge deliveries do not require it. After you rotate the client secret, the docs say the new secret can take up to an hour to be used for the digest, so a short overlap period may produce mismatches.
Response time and retries
Return a 200 quickly. Any non-200 response, including a 3XX, counts as an error. Shopify allows one second to connect and five seconds for the whole request. For bursts or slow work, queue the payload and process it after responding.
On no response or an error, Shopify retries 8 times over the next 4 hours. After 8 consecutive failures, a subscription created through the Admin API is automatically deleted, and warning emails go to the app's emergency developer email address.
Duplicates
Use X-Shopify-Webhook-Id to detect repeat deliveries: store each ID you process and skip repeats. X-Shopify-Event-Id is shared across deliveries from the same merchant action, so use it for correlation and not for deduplication.
Mistakes to avoid
- Verifying a parsed JSON body.
- Using a plain equality check for the signature.
- Doing slow work before replying, then losing the subscription after repeated timeouts.
- Treating a webhook as trusted because the URL is hard to guess.
Quick check
Why must I use the raw body?
The signature was computed over the exact bytes Shopify sent. Parsing and re-serializing changes them, so the digest will not match.
Can I use the event ID to skip duplicates?
No. Shopify says the webhook ID is for deduplication, and the event ID is for correlation.
Sources
Last reviewed: 19 October 2026.