Webhook signatures, demystified
See how every webhook is signed.
One reference for how ten providers build and verify a signature, and one zero-dependency library to verify them in code. Paste a payload and secret below, then flip a byte and watch it break.
npm i hooksig
Try it โ live, in your browser
Request โ edit anything
Verify this in code
import { verify } from "hooksig";
const result = await verify("stripe", {
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
How it works
-
Install
One package, zero runtime dependencies.
npm i hooksig -
Verify
The same typed call for every provider. Pass the raw body, headers, and your secret.
import { verify } from "hooksig"; const result = await verify("stripe", { payload: rawBody, // the RAW request body, not parsed JSON headers: request.headers, secret: env.STRIPE_WEBHOOK_SECRET, }); if (!result.verified) { return new Response(result.reason, { status: 400 }); } -
Ship
It is built on WebCrypto alone, so it runs unchanged on Cloudflare Workers, Bun, Deno, and Node.
Providers ยท 10
- stripe HMAC-SHA256 over `{timestamp}.{rawBody}`, hex-encoded. The whsec_ endpoint secret is used verbatim as the key. โ
- github HMAC-SHA256 over the raw body, hex-encoded, prefixed `sha256=`. No timestamp. โ
- standardwebhooks HMAC-SHA256 over `{id}.{timestamp}.{rawBody}`, base64-encoded. The key is the base64-DECODED bytes of the whsec_ secret. โ
- shopify HMAC-SHA256 over the raw body, base64-encoded, keyed by the app's API secret. No timestamp. โ
- slack HMAC-SHA256 over `v0:{timestamp}:{body}`, hex, prefixed `v0=`. The timestamp arrives in its own header. โ
- square HMAC-SHA256 over `{notificationURL}{body}`, base64, keyed by the webhook signature key. โ
- twilio HMAC-SHA1 over the URL followed by the POST params sorted by name and concatenated, base64. Keyed by the Auth Token. โ
- paypal RSA (SHA256withRSA) over `{transmissionId}|{transmissionTime}|{webhookId}|crc32(body)`, verified with PayPal's public key. โ
- cloudflare HMAC-SHA256 over `{time}.{body}`, hex, in a `time=,sig1=` header. Keyed by the webhook secret. โ
- vercel HMAC-SHA1 over the raw body, hex, keyed by the integration client secret. No timestamp. โ
Why hooksig
- One API, ten providersLearn one call and verify Stripe, GitHub, Svix, Shopify, Slack, Square, Twilio, PayPal, Cloudflare, and Vercel.
- Zero dependenciesNothing but WebCrypto. No node built-ins, no supply chain to audit.
- Edge-nativeRuns where the Node SDKs cannot: Cloudflare Workers, Bun, Deno, and Node 20+.
- Cross-checkedEvery scheme is verified by both a TypeScript and a Rust implementation against shared test vectors.
Pricing
$0
Free and MIT licensed, forever. hooksig is a library you run, not a service you pay for. The reference site is free too.