Webhook signatures, done properly
30 July 2026 · 5 min read
Sign the timestamp with the body
A signature over the body alone is valid forever. Anyone who captures one request can replay it whenever they like. Put a timestamp inside the signed material and reject anything old.
X-TMailr-Signature: t=1785469192,v1=<hmac-sha256 of "{t}.{body}">The receiving mistakes
- Verifying against a re-serialised body. Your JSON parser will not reproduce the exact bytes; keep the raw body and verify that.
- Comparing signatures with ==. Use a constant-time comparison.
- Accepting any timestamp. Without a window, the replay protection does nothing.
- Trusting an unsigned retry. Retries must be signed too, with a fresh timestamp.
And on the sending side
If you accept a URL from a user and then fetch it from inside your network, you have built an SSRF unless you check where it resolves. Block loopback, private ranges, link-local and the cloud metadata address, refuse redirects, and re-check on every attempt because DNS can be repointed after you approved it.
More on for developers
Testing signup emails in CI without a real mailbox
Sleep-and-hope is why your email tests are flaky. Here is the shape that is not.
Rate limiting on the wrong axis is worse than none
Per-IP limits punish shared offices and mobile networks while doing nothing to an attacker who rotates addresses for pennies.
Designing a signup form that does not leak who is registered
"That email is already taken" is a feature request and a privacy hole, and you usually have to pick.