HMAC Generator & Webhook Signature Verifier
Compute an HMAC with SHA-1/256/384/512, or paste a webhook signature header and see exactly which bytes were signed and whether it matches.
Whatever your provider calls its signing key. UTF-8 text unless the docs say hex or base64.
About HMAC Generator & Webhook Signature Verifier
This HMAC generator computes a keyed message authentication code from a payload and a secret, and reads it back as hex, base64, or base64url. It doubles as a webhook signature verifier: paste the signature header a provider sent you and the tool says whether it matches, and if not, which of your assumptions is wrong. A hash generator computes an unkeyed digest, which answers 'is this file intact?'. An HMAC is a keyed authentication code, which answers 'did the party holding this secret actually send this?'. That difference is why webhook senders sign with HMAC-SHA-256 rather than a plain SHA-256 digest. The payload is signed exactly as pasted. Nothing is trimmed, reformatted, or newline-normalized, because a single extra byte changes every byte of the digest, and a trailing newline that an editor added is the most common reason a signature that should match does not. A strip under the payload box counts the bytes and flags CRLF line endings, a BOM, and that trailing newline, so you can rule those out first. The presets build what each provider signs. Stripe signs the timestamp, a dot, then the raw request body, and sends hex in a t= and v1= header. GitHub signs the raw body only and prefixes sha256=. Slack signs v0:timestamp:body, usually over a form-urlencoded body. Shopify signs the raw body but sends base64, keyed with the app client secret. A custom template with {body} and {timestamp} covers the rest, so the same screen works as a plain HMAC calculator for partner APIs that assemble their own signing string. One limitation: the browser's Web Crypto API has no MD5, so HMAC-MD5 cannot be computed here. The hash generator covers MD5 as an unkeyed digest. Signing happens in your browser with the Web Crypto API. The secret and the payload never reach a server, and nothing you paste is sent anywhere.
How to use the HMAC generator
- Pick a preset, or stay on Custom. Custom signs the payload exactly as pasted, which is what most APIs want. A provider preset sets the signing-string template, the algorithm, the output encoding, the header format, and the replay window in one go.
- Paste the raw request body exactly as it arrived. Do not reformat it and do not let an editor add a trailing newline. The strip under the box reports the byte count and flags CRLF line endings, a BOM, and a trailing newline.
- Enter the signing secret and tell the tool how it is encoded: UTF-8 text, hex, or base64. A base64 secret used as text produces a valid-looking signature that will never match. Each preset names the credential it wants, because a signing secret means a different thing at each provider: Stripe's endpoint signing secret, GitHub's webhook secret token, Slack's app signing secret, and for Shopify the app client secret.
- For Stripe or Slack, supply the timestamp. Stripe carries it in the t= element of the same header, so if you paste the whole Stripe-Signature header the tool picks it up and tells you it did. Slack sends it in a separate X-Slack-Request-Timestamp header, so that one has to be typed in. A timestamped signing string with no timestamp produces no signature at all, on purpose: the digest would be wrong in a way that looks right.
- Read the canonical signing string panel. It shows the exact bytes about to be signed, so you can compare them against what your handler builds.
- Copy the signature as hex, base64, or base64url, or copy the whole header line in the provider's format (sha256=..., t=...,v1=..., v0=...).
- To check an incoming request, switch to Verify and paste the signature header you received. The whole header line works, including the name.
- If it does not match, read the diagnostics. The tool has already re-signed your payload with all four algorithms in all three encodings. It names the combination that matches, or reports that none of the twelve do, which points at the secret or the signing string rather than the algorithm.
- For a compact JWS, choose the JWS signing-input preset and paste the whole token. The tool splits it, signs the first two segments, and compares the third.
Common Use Cases
Build a test webhook by hand
Sign a fixture body with your endpoint secret and copy the header value. Paste it into curl or Postman to exercise your handler without waiting for the provider to send a real event.
Find out why verification fails in your handler
The usual causes are a reparsed body, the wrong output encoding, the wrong secret encoding, and a stale timestamp. The byte inspector and the algorithm sweep tell them apart in one pass, so you are not guessing.
Sign requests to a partner API
Plenty of APIs outside the webhook world want an HMAC over a string you assemble yourself. The custom template takes {body} and {timestamp} with arbitrary literals around them, which covers Adyen, Square, SendGrid, HubSpot, and internal schemes.
Check a secret rotation
Stripe sends two v1 values while both secrets are live. Paste the header and the tool reports which of them your secret produced, so you can tell whether the old key or the new one is still in play.
Frequently Asked Questions
What is the difference between a hash and an HMAC?
A hash generator computes an unkeyed digest, which answers 'is this file intact?'. An HMAC is a keyed authentication code, which answers 'did the party holding this secret actually send this?'. So an HMAC is neither encryption nor a plain hash. It mixes a secret key into the hash in a specific two-pass construction (RFC 2104), and like any hash it is one-way, so the payload and the key cannot be recovered from it. If you want an unkeyed digest of a file or a string instead, use the hash generator.
Why is my webhook signature verification failing?
In rough order of how often each one is the culprit: (1) the body was reparsed or reserialized before verification, so the bytes you signed differ from the bytes the sender signed, often by a space, a key order, or a trailing newline; (2) the wrong output encoding, usually hex where the provider sends base64; (3) the wrong secret, or the right secret in the wrong encoding, for example a base64 key used as literal text; (4) the wrong signing string, such as signing the body where the provider signs timestamp + '.' + body; (5) a timestamp outside the replay window, or a timestamp that is not the one the sender signed. This tool separates those: the byte strip settles (1), the sweep settles (2) and (4), and the timestamp row settles (5).
What exactly does Stripe sign, and what do t= and v1= mean?
In a Stripe-Signature header, t is the Unix timestamp of the event and v1 is the HMAC-SHA-256 hex digest of the timestamp, a literal dot, and the raw request body, keyed with the endpoint signing secret (whsec_...). Because the timestamp is part of the signed string, verifying with a different timestamp than the sender used fails even when the secret is right. v1 can appear twice while a secret rotation is in progress, and both should be checked. Stripe also sends a fake v0 scheme on test events, so ignore every scheme that is not v1. The default tolerance for the timestamp is 5 minutes.
Should the HMAC be hex or base64, and how do I know which one my provider wants?
Read it off the docs, or off the signature you were sent. 64 lowercase hex characters is a SHA-256 digest in hex. 44 characters ending in = is the same digest in base64. Hyphen and underscore characters mean base64url, since those replace the plus and slash of the standard alphabet. GitHub, Slack, and Stripe use hex; Shopify uses base64. The Copy as row here gives you all three from one digest, so you can try the other encoding without re-signing.
Is HS256 the same thing as HMAC-SHA-256?
Yes, with one wrinkle about what gets signed and how the result is encoded. HS256 is HMAC-SHA-256 applied to base64url(header) + '.' + base64url(payload), with the result base64url-encoded rather than hex. Pick the JWS signing-input preset, paste a compact token, and you can watch that happen: the tool signs the first two segments and compares the result against the third. To build or inspect the envelope around it, use the JWT generator and the JWT decoder.
Why do the docs say never to compare signatures with ==?
A byte-by-byte comparison that returns as soon as it finds a difference takes slightly longer for a signature with more correct leading bytes. Over many attempts that timing difference leaks the signature one byte at a time, which is enough to forge one. Use crypto.timingSafeEqual in Node, hmac.compare_digest in Python, hmac.Equal in Go, and hash_equals in PHP. One caveat about this page: the comparison here is constant-time-shaped, in that it always walks the full length, but JavaScript offers no timing guarantees, so it is not a constant-time comparison. That is acceptable here, because it runs in your own browser against your own data. It is not acceptable in your server code.
Can it do HMAC-MD5, or validate a Twilio signature?
No to both, for different reasons. The browser's Web Crypto API exposes SHA-1, SHA-256, SHA-384, and SHA-512 for HMAC and no MD5 at all, so HMAC-MD5 cannot be computed here; the hash generator computes MD5 as an unkeyed digest. Twilio's X-Twilio-Signature is not an HMAC over the request body: it is HMAC-SHA-1 over the full request URL with every POST parameter appended in alphabetical order, base64-encoded, keyed with your AuthToken, and for JSON bodies a bodySHA256 query parameter carries a SHA-256 of the body instead. That does not fit a body-plus-timestamp template, so it is left out rather than offered half-working.