Event Types
Every event payload includes atype field that identifies what occurred. The current event catalog:
Subscribe only to the event types your integration needs. Unnecessary subscriptions increase processing overhead and
expose more surface area to errors.
Delivery Model
At-least-once delivery
Every event is guaranteed to be delivered — but may arrive more than once. Your handler must be idempotent. Use
the event
id field to deduplicate.Automatic retries
If your endpoint does not return a
2xx status within the timeout window, delivery is retried with exponential
backoff across multiple attempts before the event is marked failed.Ordering is not guaranteed. An
order.ticketed event may arrive before its corresponding order.created event
under retry conditions. Always design handlers to tolerate out-of-order delivery.Request Format
Every webhook is aPOST request with the following structure:
| Field | Description |
|---|---|
id | Unique event identifier — use this for deduplication |
type | The event type from the catalog above |
created_at | ISO 8601 timestamp of when the event was generated |
data | Event-specific payload — shape varies by type |
Delivery Flow
Respond with
200 OK immediately after verification — before any business logic runs. If processing takes
longer than the timeout window, the delivery is considered failed and retried.Signature Verification
Every webhook request includes anX-Signature header. Verifying it proves the request originated from the API and was not tampered with in transit.
Signature format
t— Unix timestamp of when the request was sentv1— HMAC-SHA256 of the signed payload using your webhook secret
Signed payload construction
The string that is signed is formed by concatenating the timestamp, a literal period, and the raw request body:Verification implementation
Never use string equality (
===) to compare HMACs. Use timingSafeEqual to prevent timing attacks that could allow
an attacker to forge valid signatures.Handling Duplicates
Because delivery is at-least-once, your handler will receive the same event more than once. Guard against this by tracking processed event IDs:Store processed event IDs with a TTL that matches the maximum retry window of the delivery system (typically 72
hours). There is no need to retain them indefinitely.
Retry Schedule
When delivery fails, the system retries with exponential backoff:| Attempt | Delay | Cumulative time |
|---|---|---|
| 1 (initial) | — | 0s |
| 2 | 30s | 30s |
| 3 | 5 min | ~5 min |
| 4 | 30 min | ~35 min |
| 5 | 2 hours | ~2h 35 min |
| 6 (final) | 5 hours | ~7h 35 min |
failed and no further delivery is attempted. You can manually replay failed events from the dashboard or via the API.
Implementation Checklist
Store your webhook secret securely
Retrieve your signing secret from the dashboard and store it in an environment variable — never hardcode it.
Verify the signature on every request
Reject any request that fails HMAC verification before touching the payload.
Respond 200 before processing
Acknowledge receipt immediately. Offload business logic to a background queue.
Deduplicate using event ID
Check the event
id against your store before processing. Return 200 for duplicates without reprocessing.Best Practices
Verify every signature
Treat any request that fails signature verification as hostile. Log and discard it immediately.
Respond fast, process async
Acknowledge within the timeout window. Push processing to a queue — never block the HTTP response on business
logic.
Idempotent handlers
Design every handler to produce the same outcome when called multiple times with the same event.
Common Mistakes
| Mistake | Consequence | Fix |
|---|---|---|
| Trusting unverified payloads | Attackers can forge events and trigger unintended actions | Always verify HMAC before reading data |
| Blocking on business logic before responding | Delivery timeout triggers unnecessary retries | Return 200 first, process in a background job |
| Not handling duplicate events | Double-processing causes data corruption, duplicate charges | Deduplicate on event.id with a processed-events store |
| Using string equality for HMAC comparison | Vulnerable to timing attacks that allow signature forgery | Use timingSafeEqual or equivalent |
Ignoring the t timestamp | Replay attacks can resend valid old payloads | Reject requests where t is older than 5 minutes |
| Returning non-2xx for already-seen events | The system interprets it as a failure and retries indefinitely | Always return 200 for duplicates |

