Skip to content
Troubleshooting

Shopify webhook HMAC verification: fixing validation failures

Shopify signs every webhook using your app client secret with HMAC-SHA256, encoded as base64. Verification fails when comparing against hex or after body-parser runs.

Works with Shopify webhook events (orders, products, customers, inventory). Shopify signs every outgoing webhook using your app’s client secret and sends the calculated digest in the X-Shopify-Hmac-Sha256 header.

The #1 cause: base64 vs hex encoding

Unlike Stripe and GitHub (which send hex-encoded signatures), Shopify encodes the HMAC-SHA256 hash as a base64 string.

If your code calls .digest('hex') instead of .digest('base64'), the comparison will always fail even when the raw body and secret are 100% correct.

Verify your Shopify signature

Paste your raw request body and app client secret below to verify your digest against the X-Shopify-Hmac-Sha256 header:

Payload
Signing secret
Algorithm
Expected signature (optional)

Everything runs in your browser via the Web Crypto API. Your secret and payload are never sent anywhere.

Working code examples

Node.js / Express
import crypto from 'node:crypto';

export function verifyShopifyWebhook(rawBody: Buffer, headerHmac: string, secret: string): boolean {
  const hash = crypto
    .createHmac('sha256', secret)
    .update(rawBody)
    .digest('base64');

  // Use timingSafeEqual to prevent timing attacks
  return crypto.timingSafeEqual(Buffer.from(hash), Buffer.from(headerHmac));
}
Python (FastAPI / Flask)
import hmac
import hashlib
import base64

def verify_shopify_webhook(raw_body: bytes, header_hmac: str, secret: str) -> bool:
    digest = hmac.new(secret.encode('utf-8'), raw_body, hashlib.sha256).digest()
    computed_hmac = base64.b64encode(digest).decode('utf-8')
    return hmac.compare_digest(computed_hmac, header_hmac)

Other common failure reasons

  • Parsed JSON body: Ensure middleware like express.json() does not parse the stream before computing the hash. Always use raw bytes.
  • Wrong secret: For Custom Apps, use the API secret key. For Public Partner Apps, use the Client Secret from your Partner Dashboard.
  • Header casing: HTTP/2 lowercases headers (x-shopify-hmac-sha256). Access headers case-insensitively.
Get started

Start debugging your webhooks.

Point one endpoint at HookWatch, capture a failure, and replay it once it’s fixed. Free during beta.