Shopify Webhook HMAC Verifier
Compute and check a webhook's HMAC signature in your browser, with matching code for Node, PHP and Python.
Your details
Verification code
const crypto = require("crypto");
function verifyShopifyWebhook(rawBody, hmacHeader, secret) {
const digest = crypto.createHmac("sha256", secret).update(rawBody, "utf8").digest("base64");
const a = Buffer.from(digest);
const b = Buffer.from(hmacHeader);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
// rawBody must be the exact, unparsed request body (a string or Buffer).
// hmacHeader is req.headers["x-shopify-hmac-sha256"].Computed HMAC-SHA256 (base64): sBDGCgIvRRH0qBMcI830QHFApi/fQq4XKvyrvRwZtfY=
How Shopify signs a webhook
Every webhook Shopify sends carries an X-Shopify-Hmac-SHA256 header: a base64-encoded HMAC-SHA256 digest of the raw request body, keyed with your app's client secret. You recompute the same digest on your end and compare it to the header using a constant-time comparison, never a plain ==, to avoid timing attacks.
Secret "hush", sample order payload
- Algorithm
- HMAC-SHA256, base64
sBDGCgIvRRH0qBMcI830QHFApi/fQq4XKvyrvRwZtfY=
The most common mistake is verifying against a re-serialized or pretty-printed copy of the body instead of the exact raw bytes Shopify sent. Any difference, even whitespace, changes the digest.
Where to check it
Do this check before your route parses the body as JSON: read the raw body first, verify it, and only then parse. In Express, that usually means reading the body with a raw body parser for the webhook route specifically. In PHP, read it with file_get_contents("php://input") before anything else touches the request.
Questions people ask
Why doesn't my computed HMAC match Shopify's?
Almost always because the body was already parsed, reformatted or had encoding changed before you hashed it. Hash the exact raw bytes Shopify sent, before any JSON.parse or similar.
Which secret do I use?
Your app's client secret from the Dev Dashboard (or Partner Dashboard for older apps). It's the same secret used in the OAuth flow, not a separate webhook-only key.
Is it safe to paste my client secret into this tool?
Everything runs locally in your browser using the Web Crypto-equivalent math built into this page; nothing is sent anywhere. Still, only paste a secret into tools you trust, and rotate it if you're ever unsure.
Do I need to verify webhooks from Shopify Flow or manual triggers?
Yes, every webhook delivery over HTTPS carries this header and should be verified the same way, regardless of what triggered it.
Related tools
Last reviewed 30 September 2026.
