Webhooks
Endpoint Requirements, Retries & SSRF
What your webhook endpoint must do, the retry/backoff schedule, replay, and the HTTPS/SSRF rules that reject an INVALID_URL
Merchant endpoint requirements
You must:
- Accept an HTTP POST at the URL you configured and return HTTP 2xx (200–299) on successful receipt — any other status counts as "delivery failed" and will be retried.
- Respond quickly: our client has a total 10-second timeout per request (5-second connect timeout). Responding too slowly counts as a failure → retry. We recommend you ack 2xx immediately and process asynchronously.
- Do idempotency with
event_id: theevent_id(<id>:<event_type>) is stable for one event throughout. Retries/replays send the sameevent_id— if you have already processed it, return 2xx without doing the work again (e.g. store receivedevent_ids in your DB with a unique constraint).
Never trust a single delivery — process by
event_id idempotently. Retries and replays re-send the same event_id; if you have already handled it, return 2xx and do not re-apply the effect.How the system retries
- If you return non-2xx, time out, or are unreachable → the system retries with exponential backoff.
- Maximum 9 attempts (the first one + retries) per this backoff schedule:
retry # delay before re-sending 1 10 seconds 2 1 minute 3 5 minutes 4 30 minutes 5 2 hours 6 6 hours 7 12 hours 8 24 hours
The total retry window is roughly 24 hours. After that the event is considered terminalfailed(it is logged). - Circuit breaker: if your URL fails 5 times in a row, the system "opens the breaker" and temporarily stops sending to that URL for 1 minute, then tries again (to avoid hammering a downed endpoint) — this does not affect each event's 24-hour deadline.
- Replay: you can re-send the same event from the delivery log page in the Portal (the system enqueues a new delivery with the same
event_id).
You can view the delivery history (delivery log) on the Webhook page in the Portal — filterable by status. Each record has: event_id, event_type, status (delivered / retrying / failed), attempts, created_at (no payload, to prevent PII leaks).
URL requirements (HTTPS + SSRF)
The destination URL must pass these conditions (checked at configuration time → returns 422 INVALID_URL if it fails, and re-checked at real dial time as a backstop):
- Must be
https://only —http://is rejected, and redirects that downgrade to non-https are blocked. - Must not point to internal/private addresses (SSRF protection) — hosts that resolve to these IPs are rejected:
- loopback (
127.0.0.1,::1) - RFC1918 private (
10.0.0.0/8,192.168.0.0/16,172.16.0.0/12) - link-local / cloud metadata (
169.254.0.0/16, e.g.169.254.169.254) - carrier-grade NAT RFC 6598 (
100.64.0.0/10) - unspecified (
0.0.0.0) and IPv6 ULA (fc00::/7)
- loopback (
- Redirects may be followed up to 5 hops, and every hop must be https.
Your endpoint must therefore be a public host with TLS (a real domain + a valid certificate). You cannot use
localhost, an internal IP, or a tunnel that resolves to a private IP.Pending confirmation — if you need to test locally, specify the recommended method (e.g. a public HTTPS tunnel that resolves to a public IP)
