Skip to main content

HMAC: Hash-based Message Authentication Code

HMAC (Hash-based Message Authentication Code) is a keyed hash. A message and a secret key are combined and run through a hash function such as SHA-256, producing a short code. Anyone holding the same key can recompute that code and confirm the message is authentic and unmodified. Anyone without the key cannot produce it.

The construction is defined in RFC 2104, and it hashes twice: first over the key mixed with an inner pad and the message, then over the key mixed with an outer pad and the digest from the first pass. That second pass is why HMAC-SHA256 is safe where naively hashing a secret and a payload together is not, because the nested form resists the length-extension attacks that Merkle-Damgård hash functions are vulnerable to.

Building webhooks?
Svix is the enterprise ready webhooks sending service. With Svix, you can build a secure, reliable, and scalable webhook platform in minutes. Looking to send webhooks? Give it a try!

What HMAC proves, and what it does not

Two properties fall out of the construction. Integrity: change one byte of the message and the recomputed code no longer matches. Authenticity: only a holder of the secret can produce a code that verifies, so a matching value identifies the sender.

Three things it does not do. It does not hide the message, since the payload travels in the clear alongside its code, so HMAC belongs on top of HTTPS rather than instead of it. It does not give you non-repudiation, because the key is symmetric and the verifier could have produced the same code; a dispute over who sent what needs asymmetric signing such as Ed25519. And it says nothing about freshness, because a captured message and its code stay valid forever unless a timestamp is part of what was signed.

Why webhook providers sign with HMAC

Verification is local and cheap. The receiver already holds the secret, so checking a request is one hash over a few kilobytes: no round trip to a token endpoint, no certificate authority in the path, and nothing to keep in sync beyond the secret itself. A provider fanning out millions of events per day pays one hash per delivery, and a receiver behind any language's standard library can verify without a dependency.

The secret is also scoped naturally. Each webhook endpoint gets its own key, so one customer's leaked secret lets an attacker forge messages to that endpoint and nowhere else.

HMAC in webhooks

The sender computes an HMAC over the request body, almost always with SHA-256, and sends the result in a header: webhook-signature under the Standard Webhooks spec, X-Hub-Signature-256 from GitHub, Stripe-Signature from Stripe. The receiver looks up the secret for that endpoint, recomputes the HMAC over the exact bytes it received, and compares the two in constant time.

Getting those details right is where verification usually goes wrong, and webhook signatures covers each of them: signing the raw body rather than re-serialized JSON, comparing with hmac.compare_digest or crypto.timingSafeEqual instead of ==, including a timestamp so replays expire, and accepting two signatures during a secret rotation.

HMAC compared with other webhook authentication

Basic authentication and API keys send a shared credential on every request. They identify the caller, but they say nothing about whether the body was modified in transit, and any proxy, log line, or crash report that captures headers leaks the credential itself.

JWTs carry signed claims and an expiry, which is useful when the receiver needs to know which user an action belongs to. A webhook receiver has a narrower question: did this body come from this sender. HMAC answers exactly that without the key distribution and validation machinery.

mTLS is stronger than both, proving each end with certificates at the transport layer. The cost is certificate issuance, rotation, and renewal on a receiver you do not control, which is why providers that offer it usually keep it for enterprise customers who ask.

That balance is why HMAC signatures are the default recommendation in webhook authentication and in our authentication best practices. Svix signs every message it sends with HMAC-SHA256 over a timestamped payload, and its SDKs verify on the receiving side so neither end hand-rolls the comparison.