Verify Inbound Webhook Signatures From Any Provider

Paste the raw request body, your signing secret, and the exact signature header your endpoint received. This tool recomputes the HMAC the way Stripe, GitHub, Shopify, Slack, or your own scheme expects — then does a constant-time comparison, entirely in your browser.

How signature verification actually works

Every major webhook provider signs the raw request body with a shared secret using an HMAC keyed hash, then puts the result in a header. Verification means you compute the same HMAC on your side and check it matches. The subtlety is that each provider signs a slightly different signed payload and encodes the digest differently, so a generic "HMAC-SHA256 of the body" check silently fails for half of them.

Stripe sends Stripe-Signature: t=1700000000,v1=hexdigest. The signed payload is the timestamp, a literal period, and the body: signed = t + "." + payload, hashed with HMAC-SHA256, hex-encoded, compared to v1. Stripe also expects a replay check — reject if |now - t| exceeds your tolerance (default 300s). GitHub signs the body directly and prefixes the header with sha256=. Shopify is the odd one out: it HMAC-SHA256s the body but sends the digest base64-encoded, not hex. Slack builds a basestring v0:timestamp:body, signs it, and prefixes v0=. Custom schemes are usually a bare hex digest over the body with SHA-256 or SHA-1.

This tool parses the header for the scheme you pick, pulls out any embedded timestamp, reconstructs the exact signed payload, runs it through the browser's SubtleCrypto.sign("HMAC", …) API with the correct hash, and encodes the digest to match the provider. The comparison is constant-time (it XORs every byte regardless of where the first mismatch is) so this page never leaks timing information the way a naive a === b would. Nothing is uploaded — the secret and payload never leave your machine.

Use it to debug a 400 signature mismatch in production: a trailing newline, a re-serialized JSON body, the wrong secret from a rotated key, or a clock-skewed timestamp are the usual culprits. Flip the provider dropdown to see how the same body produces completely different valid signatures per spec.

Related Tools