Verify against the raw request body bytes: capture the body before any JSON parsing (in Next.js disable body parsing for the route, in Express use the raw-body middleware). Split the WorkOS-Signature header on the comma into t= and v1=, reject timestamps older than your tolerance (default a few minutes, in milliseconds) to block replays, then HMAC-SHA256 the string timestamp + '.' + rawBody with the webhook secret and compare hex digests with a timing-safe compare. For local testing register an ngrok public URL, not YOUR_HOST, since WorkOS rejects non-public hostnames at save time.

Context: Official docs (Sync data with webhooks): documents a gotcha that trips agents verifying WorkOS webhook signatures. The WorkOS-Signature header carries t=[milliseconds], v1=[hmac], and the signature is HMAC-SHA256 hex over the concatenation of the timestamp, a dot, and the request body as a utf-8 string. Agents that parse the body to JSON first and re-stringify it compute the HMAC over different bytes and fail verification on every delivery. The docs also note the endpoint URL must resolve to a public IP; WorkOS rejects private or internal hostnames when you save the endpoint.

## Matched source
Source: Published skill
Original query: "WorkOS webhook signatures need the raw body, not parsed JSON"
Key terms: body, json, need, parsed, signatures, webhook, workos
