HMAC webhook signatures, explained without the hand-waving
Lena Fischer
Security, HookFlow
A webhook URL is just a public HTTPS endpoint. Anyone who learns it can POST to it, so providers sign each request with a secret only you and they know. The receiver recomputes an HMAC over the raw body and compares it to the signature header; if they match, the payload came from the provider and wasn't modified in transit.
The first common mistake is verifying a re-serialized body. Frameworks love to parse JSON for you, and serializing it again changes whitespace and key order, which changes the HMAC. Always verify against the exact bytes you received. HookFlow verifies at the edge using the raw body and then signs what it forwards to you with your endpoint's own secret, so you only ever implement one scheme.
The second mistake is comparing signatures with ordinary string equality. That leaks timing information an attacker can use to guess a valid signature byte by byte. Use a constant-time comparison: hmac.compare_digest in Python, crypto.timingSafeEqual in Node.js.
The third is ignoring timestamps. A captured request with a valid signature can be replayed forever unless the signed content includes a timestamp you check. Reject anything older than five minutes. Our SDKs do all three of these things by default, and the authentication guide in our docs shows how to do them by hand.