Skip to content

Webhooks · Verify signatures

Verify webhook signatures

Authenticate each webhook request with its timestamp, exact raw body, and endpoint secret before you parse or process the event.

Soon

Webhook signature verification is coming soon

This page previews the planned design. It is not available in the API yet, so the examples below will not work against it today.

Live POS webhooks are not signed today

The POS webhooks delivered to your callback URL do not carry a signature, signing secret, or timestamp header. Everything below describes the planned design, so do not make verification a required step in your integration yet. See Webhook events for what is available now.

Signature headers

HeaderPlanned value

X-Webhook-Signature

v1=<hex HMAC-SHA256 digest>

X-Webhook-Timestamp

Unix time in seconds, for example 1758641050

X-Webhook-Id

Event ID, for example evt_demo_01

Keep the raw request body

Read the exact UTF-8 bytes received from the network. JSON parsing, whitespace normalization, character conversion, or re-serialization changes the signed payload.

Verify before parsing

Disable middleware that consumes or mutates the body on the webhook route. Never compute the digest from JSON.stringify(parsedBody).

Canonical verification sequence

Read the signature, timestamp, and event ID headers.
Reject a timestamp more than 300 seconds from your clock, in either direction.
Build signed_payload as timestamp + "." + raw_body.
Compute HMAC-SHA256 of signed_payload with the endpoint secret.
Compare expected and received digests in constant time, after checking their lengths match.
Record the event ID and only then parse and enqueue the event.

Success and failure outcomes

OutcomeAction

Verified · 200

Acknowledge and enqueue once.

Invalid signature · 401

Reject without processing.

Stale timestamp · 401

Reject as a possible replay.

Malformed headers · 400

Reject and log the event ID only.

Constant-time comparison

Confirm both digests have the same length, then use a platform timing-safe equality function. Some of these functions throw on unequal lengths, and a normal string equality check can leak comparison timing.

Timestamp tolerance and replay protection

The planned tolerance is a fixed 300 seconds (five minutes), applied in both directions and measured against a synchronized server clock. Persist event IDs and reject replays of events you already processed.

Key rotation

During a controlled rotation, accept signatures from the new and previous endpoint secrets for a short overlap. Try each active secret without logging it, then remove the previous secret after traffic is verified.

Rotate without downtime

Store versioned secrets in a managed secret store and record which version verified the request—not the secret value.

Common implementation mistakes

Using the parsed body instead of raw bytes.
Including the signature header in the signed payload.
Comparing hexadecimal strings with ordinary equality.
Skipping timestamp validation or server clock monitoring.
Logging endpoint secrets, raw signatures, or full personal data.

Troubleshooting

SymptomCheck

Every request fails

The secret matches the one issued for the endpoint.

Intermittent failures

Body middleware and encoding are deterministic.

Stale timestamp

NTP synchronization on your server.

Duplicate processing

Persist the event ID before side effects.