The signature header
The signed payload is the timestamp, a dot, and the raw request body:
Verify a request
1
Read the raw body
Use the exact bytes you received. Parsing the JSON and serializing it again changes whitespace and key order, and the signature won’t match.
2
Split the header
Split
X-ReyGrid-Signature on , then each part on = to get t and v1.3
Compute the expected signature
HMAC-SHA256 of
{t}.{raw body} with your signing secret (the full whsec_… string), as lowercase hex.4
Compare and check the age
Compare with
v1 using a constant-time comparison, and reject requests whose t is more than 5 minutes away from your clock to block replays.Framework notes
Express
Express
Use
express.raw({ type: "application/json" }) on the webhook route (not express.json()), then req.body.toString("utf8") is the raw body.Next.js (App Router)
Next.js (App Router)
Flask / FastAPI
Flask / FastAPI
Flask:
request.get_data(). FastAPI: await request.body(). Both return the raw bytes to pass to verify_reygrid.Rotating the secret
Select the rotate button next to the secret on the endpoint card in Console → Webhooks. The new secret applies to the next delivery immediately, so update your server’s environment right after rotating.Best practices
- Respond fast. Return
2xxas soon as the signature checks out and process the event in the background. Requests time out after 10 seconds. - Be idempotent. Retries reuse the same event
id(also inX-ReyGrid-Delivery). Skip IDs you’ve already processed. - Don’t rely on order. Events can arrive out of order. Use
createdwhen ordering matters. - Ignore unknown types. Return
2xxfor event types you don’t handle, so new events never cause failures.