How signatures work
Every webhook delivery includes two headers for signature verification:| Header | Description |
|---|---|
X-Signature-Timestamp | Unix timestamp (seconds) when the signature was computed |
X-Signature | Hex-encoded HMAC-SHA256 of the signing payload |
Verification steps
- Extract
X-Signature-TimestampandX-Signaturefrom the request headers - Construct the signing payload:
{timestamp}.{raw_body} - Compute HMAC-SHA256 of the signing payload using your webhook secret
- Compare the computed signature with
X-Signatureusing constant-time comparison - Optionally, reject requests with a timestamp more than 5 minutes old to prevent replay attacks
Code examples
Common mistakes
Parsing the body before verification
Parsing the body before verification
Always verify the signature against the raw request body, not a parsed-and-re-serialized version. JSON serialization may reorder keys or change formatting, producing a different signature.
Not using constant-time comparison
Not using constant-time comparison
Use
hmac.compare_digest (Python), crypto.timingSafeEqual (Node.js), or hmac.Equal (Go). Regular string comparison (==) is vulnerable to timing attacks.Ignoring the timestamp
Ignoring the timestamp
Without timestamp validation, an attacker who intercepts a valid webhook can replay it indefinitely. Reject timestamps older than 5 minutes.