engineering
Webhook signing with HMAC
Verifying the sender of incoming webhooks with HMAC signatures — the pattern Stripe, GitHub, and every mature provider uses.
Webhooks are inbound HTTP requests from third parties. Without verification, anyone on the internet can POST to your webhook endpoint and trigger work. HMAC signing is how you verify the sender.
The pattern#
When a provider sends a webhook, it computes an HMAC-SHA256 signature over the raw request body using a shared secret, then includes the result in a header (commonly X-Signature or Stripe-Signature). Your endpoint recomputes the same HMAC locally and compares — if the signatures match, the request is authentic.
The critical detail: you must verify against the raw body bytes, not a parsed-then-serialized copy. Even whitespace differences break the signature.
Timestamped signatures#
A plain HMAC is still replayable — an attacker who captures one request can send it again. Stripe solves this by prefixing the signed payload with a timestamp and rejecting signatures older than five minutes. The payload-to-sign becomes {timestamp}.{body} and the header carries both the timestamp and the signature.
Rotating secrets#
Store multiple active signing secrets and verify against each until one matches. When rotating, issue a new secret, update both sides, then deactivate the old one on the next deploy.
Do not skip this#
An unverified webhook endpoint is a remote code execution waiting to happen.