An HMAC is a keyed hash. It combines a secret key with a hash function such as SHA-256. The result is a tag. The tag proves two things: the message came from someone who holds the key, and nobody changed the message. A plain hash can't do this. An attacker can change the message and simply compute the hash again.
To verify webhooks:
- The provider and you share a secret.
- The provider computes an HMAC over a timestamp plus the raw body. It sends the HMAC in a header.
- You compute the HMAC again over the exact raw request bytes. Do this before JSON parsing, because parsing can change the bytes.
- Compare the two values with
crypto.timingSafeEqual, which takes the same time wherever they differ. - Reject requests whose timestamp is more than a few minutes old. This blocks replays (an attacker sending an old valid request again).
- Record the ids of events you have already processed. This makes handling idempotent: the same event processed twice has the same effect as once.
To rotate the secret, accept signatures from both the old and the new secret for a short transition period.
- An HMAC is a keyed hash that proves the message is unchanged and came from the key holder.
- An attacker can recompute a plain hash after changing the message.
- Compute the HMAC over the raw body and compare it in constant time.
- Use a timestamp and event ids to block replayed requests.
- When rotating the secret, accept both old and new secrets for a while.
Read more