Never trust an inbound POST just because it hits your webhook path. Every delivery is signed with your endpoint’s secret — reject anything with a missing or invalid signature before parsing the body.
Signature scheme
v1 = HEX(HMAC_SHA256(secret, "{t}.{rawJSON}")) — the timestamp, a literal ., and the exact raw request body bytes.
- Always verify against the raw body, not a re-serialized object — whitespace differences break the HMAC.
- Reject signatures older than ±300 seconds to close the replay window.
Compare digests in constant time (timingSafeEqual / hmac.compare_digest) and check the length first — a naive equality check leaks the secret to timing attacks, and some compare functions throw instead of returning false on length mismatch.
SDK helpers (recommended)
Both helpers enforce the ±300s tolerance and constant-time comparison (tolerance_seconds / toleranceSeconds is configurable).
Manual verification
If you cannot use the SDKs, the algorithm is three steps — split the header, check freshness, recompute and compare: