What is HMAC? Signing webhooks, links and API calls

HMAC proves a message is unaltered and came from a key holder. How HMAC-SHA256 works, verifying Razorpay and Cashfree webhooks, signing video links, and mistakes to avoid.

8 min read
On this page 11 sections
  1. What HMAC is
  2. How HMAC works
  3. Why not just hash the key and the message together?
  4. Key length and truncation
  5. HMAC vs hash vs digital signature
  6. Verifying payment webhooks
  7. A safe order of checks
  8. Signing URLs and tokens
  9. Mistakes to avoid
  10. Key takeaways
  11. Frequently asked questions

HMAC (hash-based message authentication code) is a way to prove that a message hasn't been changed and was created by someone who holds a shared secret key. The sender computes HMAC(key, message) with a hash function such as SHA-256 and sends the result along as a signature; the receiver recomputes it with the same key and checks that the two match. Payment gateways use HMAC to sign webhooks, CDNs to sign video links, and APIs to sign requests.

What HMAC is

HMAC was defined in RFC 2104 in 1997 and later standardised by NIST in FIPS 198-1 (RFC 2104). It can be built on any cryptographic hash, which is why you see names like HMAC-SHA256 and HMAC-SHA1. HMAC-SHA256 produces a 32-byte tag, usually sent as 64 hexadecimal characters or as Base64.

HMAC gives you two guarantees:

  • Integrity: if even one byte of the message changes, the tag no longer matches.

  • Authenticity: only someone who knows the key could have produced a valid tag.

It does not give secrecy. The message travels in readable form, so HMAC is often combined with HTTPS or encryption. An HMAC is a keyed version of a hash, not a form of encryption; our guide to what hashing is covers hashes themselves.

How HMAC works

HMAC runs the hash twice, mixing the key in each time:

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

  • K′ is the key, padded to the hash's block size, which is 64 bytes for SHA-256. Keys longer than the block are hashed first.

  • ipad is the byte 0x36 repeated, and opad is the byte 0x5C repeated. XORing the key with each gives two different derived keys.

  • The inner hash processes the key and the message; the outer hash processes the key again with the inner result.

Why not just hash the key and the message together?

It is tempting to compute SHA-256(secret + message) and call it a signature. That is unsafe. SHA-256 processes data block by block and its output is its full internal state, so anyone who sees the hash of secret + message can compute a valid hash for that message with extra data appended, without knowing the secret. This is called a length-extension attack. HMAC's nested structure stops it, which is the main reason HMAC exists.

Key length and truncation

RFC 2104 says keys shorter than the hash output are strongly discouraged, so an HMAC-SHA256 key should be at least 32 random bytes. If you shorten the tag to save space, the RFC recommends keeping at least half the hash length and never fewer than 80 bits.

HMAC vs hash vs digital signature

AspectPlain hashHMACDigital signature
KeyNoneOne shared secretPrivate key signs, public key verifies
Who can create a valid valueAnyoneAnyone holding the shared keyOnly the private-key holder
Who can verifyAnyoneOnly holders of the same keyAnyone with the public key
Proves who sent it?NoYes, between the two partiesYes, even to outsiders
SpeedVery fastVery fastMuch slower
Typical usesFile checksums, deduplicationWebhooks, API requests, signed URLs, session tokensApp signing, certificates, signed documents

The key difference is who can create a valid tag. With HMAC, the payment gateway and your server both hold the webhook secret, so either could have produced any given tag. That is fine between two parties that trust each other, but it can't prove to an outsider which side signed. For that you need a digital signature; see how digital signatures work.

Verifying payment webhooks

When a student pays, the gateway calls your server with a webhook saying the payment succeeded. Anyone on the internet can send a request to that URL, so the signature is what separates a real payment from a forged "payment.captured" message. Two gateways widely used in India do it like this:

GatewayHeaderWhat is signedOutput
RazorpayX-Razorpay-SignatureThe raw request body, with HMAC-SHA256 keyed by your webhook secretHex
Cashfreex-webhook-signature, with x-webhook-timestampThe timestamp followed directly by the raw body, with HMAC-SHA256 keyed by your secret keyBase64

Both gateways' documentation stresses the same point: sign and verify the raw body exactly as received, not JSON you have parsed and re-serialised, because any change in spacing or key order breaks the match (Razorpay, Cashfree). A Razorpay-style check in Python looks like this:

import hashlib
import hmac

def webhook_is_genuine(raw_body: bytes, received: str, secret: str) -> bool:
    expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, received)

# In your webhook handler: pass the raw request bytes, the
# X-Razorpay-Signature header and your webhook secret

A safe order of checks

Say a student pays ₹14,999 for a UPSC foundation batch. Before your system grants access, it should:

  1. verify the signature against the raw body, and reject the request if it fails;

  2. check that the event ID hasn't been processed before, since gateways retry deliveries (Razorpay sends an x-razorpay-event-id header for this);

  3. confirm the order ID, amount and currency match the order you created, not just that some payment succeeded;

  4. only then mark the order paid and enrol the student in the batch.

Razorpay also returns a signature to the checkout page after payment, an HMAC-SHA256 of the order ID and payment ID joined by a |, and its documentation calls verifying it on your server mandatory. For what these gateways charge and how settlement works, see our guide to payment gateways.

Signing URLs and tokens

The same idea protects links to paid content. A signed video URL carries the file path, an expiry time and often a user or session ID, plus an HMAC over all of them. The server or CDN recomputes the HMAC and refuses the request if anything was edited or the link has expired. Google's Cloud CDN, for instance, signs URLs with HMAC-SHA1 and a 16-byte key over the full URL, including its Expires and KeyName parameters. HMAC-SHA1 is still considered safe as a MAC even though SHA-1 is broken for collisions, though SHA-256 is the better choice for new designs. Other CDNs use public-key signatures instead: CloudFront signed URLs use RSA-2048 or ECDSA-256 key pairs. Our guide to signed URLs covers expiry times and streaming formats.

JSON Web Tokens signed with HS256 are HMAC-SHA256 too. Anyone who holds the secret can mint valid tokens, so every service that verifies them can also forge them. When several services need to verify tokens but only one should issue them, use an asymmetric algorithm such as RS256 or ES256. Remember that a signed token's contents are only Base64-encoded, not hidden; see why Base64 isn't encryption.

Mistakes to avoid

  1. Comparing tags with ==. An ordinary string comparison stops at the first differing character, and the timing difference can leak how much of a forged tag is correct. Use a constant-time comparison such as Python's hmac.compare_digest (Python docs) or Node's crypto.timingSafeEqual.

  2. Verifying re-serialised data. Always verify the exact bytes you received.

  3. No protection against replays. A valid signed message can be sent again. Include a timestamp or expiry inside what is signed, reject stale messages and keep a record of processed event IDs.

  4. Signing too little. A video link signed without an expiry, or a payment callback verified without checking the amount, is still exploitable.

  5. Putting the secret in the app. An HMAC key shipped inside a mobile app or website JavaScript can be extracted, and then anyone can sign. Keep HMAC keys on servers.

  6. One key for everything. Use separate keys for webhooks, links and tokens, generate them randomly, and rotate them. Stripe, for example, lets an old webhook secret stay valid for up to 24 hours while you switch.

  7. Treating HMAC as encryption. It hides nothing. Anything sensitive in the message still needs HTTPS or encryption.

Key takeaways

  • HMAC proves a message is unaltered and came from a key holder; it does not hide the message.

  • Its nested design defeats the length-extension attack that breaks SHA-256(secret + message).

  • Verify payment webhooks against the raw body with a constant-time comparison, then check event ID, amount and order before granting access.

  • Signed URLs and HS256 tokens are HMACs; include expiry and user details in what you sign.

  • Use digital signatures when verifiers shouldn't be able to create valid signatures themselves.

Frequently asked questions

What is HMAC and how does it work?

HMAC is a message authentication code built from a hash function and a secret key. The sender mixes the key into the message and hashes it twice, producing a short tag. The receiver, who holds the same key, repeats the calculation and compares tags. If they match, the message wasn't changed and came from a key holder. HMAC-SHA256 is the version you will see most often.

What is HMAC authentication?

HMAC authentication means proving who sent a request by attaching an HMAC tag computed with a shared secret. APIs and webhooks use it: the client or payment gateway signs each request, often including a timestamp, and the server recomputes the tag and rejects requests that don't match or are too old. The secret itself is never sent, so intercepting one request doesn't reveal it.

What is an HMAC key?

An HMAC key is the shared secret both sides use to create and check tags. It should be random, at least as long as the hash output (32 bytes for HMAC-SHA256), kept on servers rather than in apps, and different for each purpose. Payment gateways call it the webhook secret or secret key. Anyone who learns it can forge valid tags, so rotate it if it leaks.

What is HMAC verification?

HMAC verification is the receiver's side of the process. It takes the message exactly as received, recomputes the HMAC with its copy of the key, and compares the result with the tag that arrived, using a constant-time comparison. If the two match, the message is accepted; if not, it is rejected. Good verification also checks timestamps or event IDs, so an old valid message can't be replayed.

Share this article

Looking for something else?

Talk to Us