Skip to content

HMAC generator

Generate HMAC-SHA1, SHA-256, SHA-384 and SHA-512 codes from a secret and a message, in hex or Base64 — ideal for debugging webhooks.

  • Runs in your browser
  • Nothing you enter is sent
  • Free, no account
Key
The shared secret. Both sides of an HMAC must hold the same key — that is what makes the code unforgeable by anyone who does not.
Message
The exact bytes the sender signed. A single trailing newline changes the code completely, which is the usual cause of a webhook signature mismatch.
HMAC-SHA256 digest
Keyed authentication code · proves both integrity and origin
Enter a key and a message to see the authentication code.

Paste the checksum published next to a download. Comparison ignores case and any spaces.

What HMAC adds to a hash

A plain hash proves nothing about who produced it, because anyone can compute one. HMAC mixes a shared secret into the hash in a specific nested construction:

HMAC(K, m) = H((K ⊕ opad) ‖ H((K ⊕ ipad) ‖ m))

The two padded copies of the key are what make it secure: the construction is provably resistant to length-extension attacks, which is why you must use HMAC rather than simply hashing a secret and a message together.

Debugging a webhook signature

This is what most people are here for. When a provider says your signature does not match, the cause is almost always one of these, in order of likelihood:

  1. The body is not the raw body. Your framework parsed the JSON and re-serialised it, changing key order or whitespace. Capture the bytes before any parsing.
  2. A prefix is missing. Stripe signs timestamp + "." + payload. GitHub signs the body alone but prefixes the header value with sha256=. Read the exact construction in their docs.
  3. The key format is wrong. A secret that looks like hex may be intended as UTF-8 text, or the reverse. Try all three formats in the selector above and see which reproduces the expected signature.
  4. Encoding of the output. Some providers send hex, others Base64. The value looks completely different but may be the same bytes.
  5. A trailing newline. One extra byte changes the whole code.

Common provider constructions

ProviderAlgorithmSigned valueOutput
StripeHMAC-SHA256t.payloadHex
GitHubHMAC-SHA256Raw bodyHex, prefixed sha256=
ShopifyHMAC-SHA256Raw bodyBase64
SlackHMAC-SHA256v0:timestamp:bodyHex, prefixed v0=
AWS SigV4HMAC-SHA256, chainedA derived key over four stagesHex

Always verify against the provider's current documentation — these constructions do change, and the details are exactly where the bugs live.

Comparing signatures safely

In production code, never compare with ==. A byte-by-byte comparison returns early on the first mismatch, and the timing difference is measurable over enough requests, which lets an attacker recover a valid signature one byte at a time. Use your platform's constant-time comparison: hmac.compare_digest in Python, crypto.timingSafeEqual in Node, hash_equals in PHP.

The comparison on this page is a debugging aid, so a plain equality check is fine here.

Also verify the timestamp

A valid signature does not make a request fresh. Without a timestamp check, an attacker who captured one webhook can replay it forever. Reject anything older than a few minutes, and include the timestamp inside the signed value so it cannot be altered — which is exactly why Stripe and Slack sign it alongside the body.

Pasting a production secret

The secret stays in this tab: WebCrypto runs locally, and the page cannot make a network request. Use a test key where you have one — not because of this page, but because anything on screen can be captured by a screenshot tool or seen over your shoulder.

Questions

What does HMAC add over a plain hash?

A key. Anyone can compute SHA-256 of a message, so a bare hash proves nothing about who sent it. HMAC mixes a shared secret into the hash in a specific nested construction, so only a holder of the key can produce a valid code.

My webhook signature will not match. What is wrong?

Nearly always the message bytes. Sign the exact raw body — before any JSON parse and re-serialise — including a trailing newline if there is one. Many providers also prefix a timestamp and a dot before the payload.

Which key format should I pick?

Whatever the provider gave you. Stripe and GitHub secrets are UTF-8 text; AWS derives a binary key you would supply as hex or Base64. Choosing the wrong format silently produces a wrong code.

Is it safe to paste a production signing secret here?

The secret stays in the tab: WebCrypto runs locally and the page has no network access. It is still worth using a test key where you can, because anything on screen can be shoulder-surfed or captured by a screenshot tool.

Should I compare signatures with a simple equality check?

Not in production code — use a constant-time comparison to avoid leaking timing information. On this page the comparison is for debugging, so a plain check is fine.