hooksig / Shopify
HMAC-SHA256 · base64 · no timestamp
Shopify
Every Shopify app wires this up, and base64 instead of hex is what trips people.
Try it — live, in your browser
Request — edit anything
Verify this in code
import { verify } from "hooksig";
const result = await verify("shopify", {
payload: rawBody, // the RAW body, not parsed JSON
headers: request.headers,
secret: env.WEBHOOK_SECRET,
});
if (!result.verified) {
return new Response(result.reason, { status: 400 });
}no match — signature invalid
Signed string
HMAC-SHA256( secret, signed string ) = expected
Expected vs provided
expected
provided
How it's built
The scheme
- Header
X-Shopify-Hmac-Sha256 - AlgorithmHMAC-SHA256
- Encodingbase64
- Timestampnone — add your own if you need it
- SignsHMAC-SHA256 over the raw body, base64-encoded, keyed by the app's API secret. No timestamp.
Gotchas
- The digest is base64, not hex. Comparing the wrong encoding fails silently.
- Sign the RAW body. Express's json() parser consumes it, so capture the raw buffer first.
- The header carries the digest directly, with no sha256= prefix.
- No timestamp is signed, so there is no built-in replay protection.