1.
Read the raw request body — the exact bytes received, before any JSON parsing or re-serialization. Reformatting the JSON changes the bytes and breaks the signature.
2.
Read the X-Timestamp and X-Signature headers.
3.
Reject the request if X-Timestamp is outside your tolerance window (5 minutes is a good default), to block replays.
4.
Compute HMAC-SHA256 over {X-Timestamp}.{raw_body} with your signing secret.
5.
Compare your result to X-Signature using a constant-time comparison.
6.
If they don't match, respond 400 and do not process the event.
Signing the parsed body. Verify against the raw bytes, not a re-encoded object — key order and whitespace differ and the hash won't match.
Non-constant-time comparison. Use hash_equals / timingSafeEqual, not ==, to avoid timing attacks.
No timestamp check. Without the tolerance window, a captured request can be replayed indefinitely.
Trusting before verifying. Verify first, then parse and act on the event.