My Tool Studio
Developer Tools·4 min read

How to Verify a Webhook Signature With HMAC

Every webhook endpoint is a public URL, which means anyone who finds it can post to it. Signatures are how you tell a real event from a forged one, and nearly every provider signs with HMAC: a hash of the request body mixed with a secret only you and the sender know. This guide explains what an HMAC is, walks through a real signature you can reproduce in the HMAC Generator, and lists the small mistakes that make a perfectly valid signature refuse to match.

HMAC{"id": 46"ok": true}

Why a webhook needs a signature

The problem it solves.

Picture an online shop that marks orders as paid when its payment provider calls /webhooks/payments. The endpoint has to be reachable from the internet, so nothing stops a stranger from sending the same JSON with a made-up order number. Without a check, that request ships goods nobody paid for.

The fix is a shared secret. When you set up the webhook, the provider gives you a signing key. For every event it computes an HMAC of the exact request body with that key and sends the result in a header. Your server computes the same HMAC from the body it received. If the two values match, the request came from someone holding the key and the body was not changed on the way.

How HMAC works, without the math

HMAC, defined in RFC 2104, wraps an ordinary hash function such as SHA-256 around the key and the message in two passes. The key is padded to the hash's block size and mixed with one constant, the message is appended and hashed, then the key is mixed with a second constant and hashed again with that first result. The output has the same length as the hash: 64 hex characters for HMAC-SHA256.

Two properties matter in practice. First, without the key you cannot produce a valid HMAC, even if you know the message and the algorithm. Second, any change to the message, even one byte, produces a completely different value. That is why a signature check doubles as an integrity check.

HMAC also sidesteps the weaknesses of older hashes. HMAC-SHA1 and HMAC-MD5 are not known to be practically broken, because HMAC does not depend on collision resistance, but new systems should still pick SHA-256 or stronger.

A worked example you can reproduce

Open the HMAC Generator and click Try sample. It fills in the key my-webhook-secret and the body {"id":"evt_1042","type":"order.paid","amount":4999}. With Algorithm set to HMAC-SHA256 and Output set to hex, the result is 0e16d51af3200994cbff80aa09e145f8477191a7c0eddf328c13ff99dad87f39. Switch Output to Base64 and the same bytes read DhbVGvMgCZTL/4CqCeFF+EdxkafA7d8yjBP/mdrYfzk=.

Now paste the hex value into Verify a signature. A green notice confirms the match. Add a single space after the first colon in the body and the HMAC becomes 8e8c241c5d6985ba2a4d2ed69d8aba811832d5e23b915e963b1f3b936bfc8020, and the check turns amber. The data means the same thing to a JSON parser, but the bytes differ, so the signature does too.

That last point is the whole game. Signatures protect bytes, not meaning, which is why the most common bug is verifying a body that was parsed and re-serialized instead of the raw bytes that arrived.

Why a valid signature fails to match

Checks to run, in order.

When your code and the provider disagree, the key and the algorithm are rarely the problem. Work through these before you rotate anything:

  • The body was parsed first. Frameworks often hand you a JSON object; re-serializing it changes spacing and key order. Read the raw body, for example with express.raw() in Node.js.
  • The provider signs more than the body. Some sign a timestamp, a dot and the body together, and put the timestamp in another header. Build the exact string their docs describe.
  • The key is in the wrong form. Some providers use the key as plain text, others want part of it Base64-decoded. Set Key is to text, hex or Base64 in the generator and see which one matches.
  • Hex compared with Base64. The same signature written two ways never matches as text. Pick the Output format your header uses.
  • A prefix in the header. Values like sha256=... carry a label; strip it before comparing. The generator ignores common prefixes for you.
  • Line endings and trailing newlines. A body saved to a file and pasted back may gain a newline at the end, and that is a different message.

Choosing an algorithm and a key

Use HMAC-SHA256 unless a provider or protocol names something else. It is what GitHub, Stripe and Shopify webhooks use, and what AWS Signature Version 4 is built on. HMAC-SHA512 is fine where it is specified, and JWTs signed with HS256, HS384 or HS512 are simply HMACs over the token's first two parts.

A key should be random and at least as long as the hash output, so 32 bytes for HMAC-SHA256. Typed phrases are far weaker than they look. The Random 256-bit key button creates one with the browser's secure random generator, in hex or Base64, ready to paste into a provider's settings and your environment variables.

Plan for rotation from the start. Accept signatures from both the old and the new key for a short overlap, then remove the old one, so a leaked key can be replaced without dropping real events.

Checking signatures safely in production

Once the values match in the generator, move the same steps into code and add two protections. First, compare signatures with a constant-time function such as crypto.timingSafeEqual in Node.js or hmac.compare_digest in Python. A plain == can return early on the first differing character, which leaks timing information.

Second, stop replays. A valid signed request captured once stays valid forever unless the signature covers a timestamp. When a provider includes one, reject events older than a few minutes and remember recent event ids so a repeat is ignored.

Finally, log enough to debug without leaking secrets: the event id, the header value and whether it matched, never the key. When a mismatch appears, paste the logged body and header into the HMAC Generator and you will usually find the difference in a minute. For plain checksums of files, with no key involved, the Hash Generator is the right tool instead.

Try it now

Open HMAC Generator

The tool is one click away. No sign up, no upload, no payment.

Open HMAC Generator