Delivery headers
Two headers are sent with every POST request to your endpoint:| Header | Format | Description |
|---|---|---|
X-Webhook-Signature | t=<unix_timestamp>,v1=<hex_hmac> | The primary signature header. Contains both the timestamp and the HMAC digest. |
X-Webhook-Timestamp | <unix_timestamp> | The same timestamp, provided as a standalone header for convenience. |
How the signature is constructed
Crypto Checkout computes the signature using the following steps:- Take the Unix timestamp (seconds since epoch) for the delivery attempt.
- Concatenate the timestamp, a literal
.character, and the raw request body string:${timestamp}.${rawBody}. - Compute
HMAC-SHA256of that string using your endpoint’s secret as the key. - Hex-encode the result and set the header to
t=${timestamp},v1=${hexDigest}.
Tolerance window
Reject any delivery where the absolute difference between thet timestamp and the current server time exceeds 300 seconds (5 minutes). This prevents an attacker from replaying a legitimately signed payload captured from an earlier delivery.
Using the SDK (recommended)
TheverifyWebhook function from the SDK handles header parsing, timestamp validation, HMAC computation, and timing-safe comparison in one call. It returns the parsed, typed event object on success and throws a WebhookVerificationError on any failure.
When using Express, register
express.raw({ type: "application/json" }) before any global express.json() middleware on this route. The express.json() middleware consumes and discards the raw body — once that happens, signature verification will always fail.VerifyOptions reference
| Option | Type | Required | Description |
|---|---|---|---|
payload | string | ✅ | The raw request body string, before JSON.parse |
signature | string | undefined | null | ✅ | Value of the X-Webhook-Signature header |
timestamp | string | number | null | — | Value of X-Webhook-Timestamp; used as fallback if t= is absent from the signature header |
secret | string | ✅ | Your endpoint’s webhook secret (whsec_…) |
toleranceSeconds | number | — | Replay window in seconds; defaults to 300 |
nowSeconds | number | — | Override the current time (seconds since epoch) used for tolerance checking; useful in tests |
WebhookVerificationError messages
The SDK throws a WebhookVerificationError (subclass of Error) with one of these messages:
| Message | Cause |
|---|---|
missing signature header | X-Webhook-Signature header was absent or empty |
malformed signature header | Header value could not be parsed — missing t= or v1= component |
timestamp outside tolerance window | Delivery is stale or the server clock is skewed by more than 5 minutes |
signature mismatch | HMAC digest did not match — payload may have been tampered with |
payload is not valid JSON | Signature checked out but the body is not parseable JSON |
Manual verification (Node.js)
If you are not using the SDK, you can reproduce the same logic with Node’s built-incrypto module:
Framework-specific notes
- Next.js App Router
- Fastify
Next.js App Router route handlers receive a
Request object. Read the body as text once before passing it to verifyWebhook:Finding and rotating your webhook secret
Your webhook secret is displayed once — immediately after you create the endpoint in the dashboard or via the API. It is stored only as a prefix (secretPrefix) in the endpoint object for identification; the full value is never retrievable after creation.
If you lose your secret:
- Delete the existing endpoint (
DELETE /api/v1/webhook_endpoints/{id}). - Create a new endpoint with the same URL and event subscriptions.
- Copy the new
secretfrom the create response and update your environment variable.
Webhooks Overview
Learn how to create and manage webhook endpoints and understand the delivery lifecycle
TypeScript SDK
Full SDK reference including the verifyWebhook function and all exported types