Signed headers
Header names are case-insensitive. Your framework may expose them in lowercase.
Signing input
Wircle signs the following UTF-8 string:whsec_ prefix in your endpoint secret. The resulting SHA-256 digest is Base64-encoded and returned with a v1, prefix.
Do not parse and re-serialize the body before verification. Whitespace, property order, and character encoding are part of the signed value, so verification must use the exact raw bytes received over HTTP.
Verification procedure
- Read
webhook-id,webhook-timestamp, andwebhook-signaturefrom the request headers. - Reject missing headers and unrecognized secret or signature prefixes.
- Parse the timestamp and reject requests outside a short tolerance. Five minutes is recommended.
- Construct the signing input using the raw request body.
- Calculate the HMAC-SHA256 digest using the decoded endpoint secret.
- Compare the expected and received signatures with a constant-time comparison.
- Parse and process the JSON only after verification succeeds.
Node.js example
false, respond with 400 or 401 and do not process the event.
Replay protection and idempotency
Signature verification proves that Wircle signed the request, but it does not prevent an already valid request from being replayed within your timestamp tolerance. After the signature is valid, usewebhook-id as an idempotency key:
- Store the ID before performing side effects.
- If the ID has already been processed, return a
2xxresponse without repeating those side effects. - Keep processed IDs for at least as long as the delivery retry window.
Retries
The body andwebhook-id stay the same across retries. Each attempt has a new webhook-timestamp, so it also has a new webhook-signature. Always verify the headers on the current request instead of storing an earlier signature.
Secret handling
- Store signing secrets only in server-side secret storage.
- Never expose a secret in browser code, logs, or error responses.
- Each endpoint has its own secret. Select the secret using trusted endpoint configuration, not an unsigned body field.
- If a secret may be compromised, create a replacement endpoint, switch traffic to it, and revoke the old endpoint.