In one sentence
An HMAC is a hash computed with a shared secret key mixed in, so it proves two things at once — the message was not altered, and it came from someone who holds the key — which is why webhooks and APIs sign their payloads with it.
Why it matters
A plain hash tells you a message was not accidentally corrupted — but anyone can recompute it, so it proves nothing about who sent the message. HMAC adds a shared secret to the hash. Now only the two parties who know the key can produce a valid code, so a matching HMAC means both "unchanged" and "from someone with the key". That combination — integrity plus authenticity — is what makes it the workhorse of webhooks, API request signing and token verification.
When a service like Stripe or GitHub sends your endpoint a webhook, it signs the raw body with a secret you both hold and puts the HMAC in a header. You recompute it and compare; if they match, you know the payload is genuine and untouched. Without that check, anyone who guesses your URL can forge events.
How it is built
HMAC is a specific, careful construction — not just "hash the key and the message together" (which is vulnerable to length-extension attacks). RFC 2104 defines it as two nested hashes with the key XORed into distinct inner and outer pads: H((K ⊕ opad) || H((K ⊕ ipad) || message)). You do not implement this by hand; every crypto library provides it.
- Name it by its hash:
HMAC-SHA256is HMAC over SHA-256. Prefer SHA-256 or better; HMAC-SHA1 is legacy and should not be used for new designs. - Output is the hash's width (32 bytes for SHA-256), rendered as hex or Base64 depending on the API.
- It is symmetric: the same secret computes and verifies. That is the crucial difference from a digital signature, where a private key signs and a public key verifies — HMAC gives no non-repudiation, because either party could have produced the code.
One implementation rule matters enormously: verify in constant time. Comparing the received and computed codes byte-by-byte with an early exit leaks timing that can be used to forge a code; use a constant-time comparison.
Try it yourself hands-on
Reproduce a webhook signature check the way a receiving server does it.
- Open the HMAC Generator. Put the webhook's raw JSON body in Message, your webhook Secret key in the secret field, choose SHA-256 (recommended), and set the output encoding (hex or Base64) to match what the sender uses. The computed HMAC is the value you compare against the sender's signature header.
- Paste the sender's signature into Expected HMAC and watch the tool report ✓ Match or ✗ Mismatch. Change one character of the message body and it flips to Mismatch instantly — that is tamper detection in action.
- Switch the algorithm to SHA-1 (legacy) and note the different, shorter output: this is why sender and receiver must agree on the exact hash. A mismatch is very often just SHA-1-vs-SHA-256 or hex-vs-Base64, not an attack.
- See HMAC in a real protocol: the JWT Decoder uses HMAC-SHA256 to verify an
HS256token — the same primitive, signingheader.payloadinstead of a webhook body.
Result: you can validate any HMAC-signed payload and diagnose the usual "signatures don't match" causes (wrong hash, wrong encoding, whitespace in the body) rather than disabling the check.
Common misreadings
- HMAC is not a signature. Both sides share the secret, so it cannot prove which party sent a message to a third party. When you need non-repudiation, use asymmetric signatures.
- Sign the exact bytes, not a re-serialised object. Reformatting the JSON before hashing changes the bytes and breaks verification — hash the raw received body.
- Compare in constant time. A naive
==on the codes can leak timing; use the library's constant-time equality (as the tool does) to avoid a byte-at-a-time forgery.