HMAC generator and webhook validator
Compute HMAC-SHA256, SHA-384, SHA-512 or SHA-1 and check whether the signature a webhook sent matches. The GitHub, Stripe, Shopify, Mercado Pago and Slack presets build the signed message the way each provider does. Payload and key stay in your browser.
HMAC of the payload as is, with the algorithm and encoding you choose.
The signature covers the raw body, byte for byte. A different space, line break or field order changes the result. Paste the body exactly as it arrived, before it went through JSON.parse or a formatter.
Signature
The key is missing
Enter the secret key. The signature appears as you type.
An HS256 JWT is an HMAC-SHA256 of the header and payload. The decoder checks the token's signature with your secret.
Want to read the webhook payload? Format it in a separate tab. Formatted JSON has different bytes and can't be used to check the signature.
Just need a hash, without a key? The hash generator computes MD5, SHA-1 and SHA-256 of text and files.
What is HMAC
HMAC is a hash computed with a secret key (RFC 2104). Anyone can recompute a plain SHA-256 after changing the message. With HMAC, only whoever knows the key can produce the right signature. That's why it's used to prove a request came from who it says and wasn't changed along the way: webhooks, API signing such as AWS Signature V4 and HS256 JWTs.
Each provider's format
Each provider chooses what it signs and how it writes the result. The table sums up this page's presets; each format was checked against the provider's official documentation.
| Provider | Header | What is signed | Format |
|---|---|---|---|
| GitHub | X-Hub-Signature-256 | body | sha256=<hex> |
| Stripe | Stripe-Signature | t.body | t=<ts>,v1=<hex> |
| Shopify | X-Shopify-Hmac-Sha256 | body | <base64> |
| Mercado Pago | x-signature | id:…;request-id:…;ts:…; | ts=<ts>,v1=<hex> |
| Slack | X-Slack-Signature | v0:timestamp:body | v0=<hex> |
How webhook verification works
The provider and your application share the same secret. When sending the event, the provider computes the HMAC of the body, or of a message built from a timestamp and the body, and sends the result in a header. Your application recomputes it with the received body and compares. If they match, the event is authentic. The comparison must run in constant time, with crypto.timingSafeEqual in Node, hash_equals in PHP or hmac.compare_digest in Python, so the response time doesn't leak the signature.
The number one cause of invalid signatures
The framework turns the JSON into an object before your code runs, and serializing it again changes key order, spacing and the escaping of accented characters. The signature stops matching even with the right key. Read the raw body: express.raw() in Express, $request->getContent() in Laravel, a String parameter with @RequestBody in Spring and request.get_data() in Flask. In NestJS, create the app with rawBody: true and use req.rawBody.
Key as text, hex or Base64
HMAC works with the key's bytes. If the provider's dashboard shows a hex secret and you use that string as text, the bytes are different and so is the signature. Most webhook providers, including GitHub, Stripe, Shopify, Mercado Pago and Slack, use the secret as text, exactly as it appears in the dashboard, including Stripe's whsec_ prefix.
Timestamps and replays
A captured valid request stays valid if it's sent again. Stripe and Slack put the timestamp inside the signed message, and their libraries reject events older than 5 minutes. When testing with an old payload copied from a log, the signature may match here and still be rejected by your server because of the time.
Frequently asked questions about HMAC
Webhook signatures, keys, encodings and the most common validation mistakes.