webhook-timestamp is more than five minutes off your clock, which stops a captured request from being sent again later.
Verify the body exactly as it arrived: parse it only after the check. Re-serialized JSON can differ by a space and fail.
Then store the event somewhere durable (a queue, a database table) before you answer 2xx, and answer an error if you can’t: a 2xx marks the delivery as succeeded, and LeadVector won’t send it again. The examples below do the work after answering, from what they stored.
With a library
By hand
Without a library, the check is:- Take the secret after
whsec_and decode it from base64: that is the key. - Build the signed content:
{webhook-id}.{webhook-timestamp}.{body}, with the raw body. - Compute HMAC-SHA256 of it with the key, and encode the result in base64.
webhook-signatureholds one or more signatures, space-separated, eachv1,and a base64 value. The delivery is genuine if any of them equals yours. Compare in constant time.- Refuse it if
webhook-timestamp(Unix seconds) is more than five minutes from now.
When a rotation overlaps
While a rotated secret’s old one still signs (overlapHours), each delivery carries a signature from each, and one that either secret verifies is genuine. Switch your receiver to the new secret any time in that window; the libraries above accept a delivery if any of its signatures matches the secret you gave them.