Verify HMAC webhook signatures from Stripe, GitHub, Shopify and Slack.
Tip: use samples, upload, copy, download, and send-to actions inside the workspace where available.
Webhook Payload Inspector Analyzer is a free, browser-based tool that helps you understand a dataset at a glance. Verify HMAC webhook signatures from Stripe, GitHub, Shopify and Slack. It's built for speed and privacy: Everything runs locally in your browser — your data is never uploaded to a server. No sign-up, no installs, and no daily limits.
Profile the data first so missing values, types, duplicates, and outliers are visible before you edit it.
Review the preview, copy or download the result, and keep everything local in your browser.
HTTP Header Analyzer: Grade security headers, explain every header, and simulate CORS preflight.
Open toolAPI Response Inspector: Inspect a response's shape, status, caching and rate-limit signals.
Open toolAPI Request Builder: Build an HTTP request visually, then export it as curl, fetch, axios, Python or Go.
Open toolGitHub: Signs the raw body with HMAC-SHA256, hex-encoded and prefixed `sha256=`. No timestamp, so replay protection is your responsibility via the delivery ID. The HMAC is computed locally with WebCrypto — your secret is never transmitted, logged or stored.
Either the secret is wrong, the body has been modified, or the signed string is assembled differently than this profile assumes.
757107ea0eb2509fc211221cce984b8a37570b6d7586c22c46f4379c8b043e17(needs a secret and a body){"ref":"refs/heads/main","repository":{"name":"dataxforge","full_name":"acme/dataxforge"},"pusher":{"name":"ada","email":"ada@example.com"}}140 bytes. This is where verification usually fails: the signature covers the raw bytes, so parsing JSON and re-serialising it — as most frameworks do automatically — changes key order and whitespace and invalidates the signature.
| Header | Value |
|---|---|
| X-Github-Event | push |
| X-Github-Delivery | 72d3162e-cc78-11e3-81ab-4c9367dc0958 |
| User-Agent | GitHub-Hookshot/044aadd |
| Content-Type | application/json |
{
"ref": "refs/heads/main",
"repository": {
"name": "dataxforge",
"full_name": "acme/dataxforge"
},
"pusher": {
"name": "ada",
"email": "ada@example.com"
}
}import crypto from "node:crypto";
// The RAW body, before any JSON parsing. Re-serialising changes the bytes
// and the signature will never match.
export function verify(rawBody: string, headers: Record<string, string>, secret: string): boolean {
const supplied = (headers["x-hub-signature-256"] ?? "").replace("sha256=", "");
if (!supplied) return false;
const expected = crypto
.createHmac("sha256", secret)
.update(rawBody)
.digest("hex");
// Always a constant-time comparison — === leaks the correct prefix by timing.
const a = Buffer.from(expected);
const b = Buffer.from(supplied);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}Verify before you parse. Capture the raw body first — in Express that means express.raw() on the webhook route specifically, because a global JSON body parser consumes the stream and leaves you unable to reconstruct the signed bytes.
Compare in constant time. A plain === returns early at the first differing byte, which leaks how much of a guessed signature was correct. Use crypto.timingSafeEqual.
A valid signature is not a fresh request. Without a signed timestamp, a captured delivery replays successfully forever. Enforce a tolerance window where one exists, and store delivery IDs to reject duplicates where it does not.
Expect duplicates regardless. Providers retry on timeout and at-least-once delivery is the norm, so handlers must be idempotent. Return 2xx quickly and do the real work asynchronously — a slow handler causes the retry that causes the duplicate.
Never trust the payload over your own records. Treat a webhook as a notification that something changed, then read the authoritative value back from the provider’s API before acting on anything that moves money.