Skip to main content
Money moves after your HTTP request returns. Webhooks are how you find out.

Setup

  1. Create an endpoint — in the dashboard under Developers → Webhooks, or:
Use ["*"] to receive everything. The response includes the endpoint’s signing secret (whsec_…); it’s also available from GET /v1/webhooks/signing-secret/{endpointId}.
  1. Respond 2xx quickly (under 15 seconds). Do the work asynchronously.
  2. Send yourself a test: POST /v1/webhooks/endpoints/{id}/test — or the Send test button in the dashboard.

Payload

data is the resource exactly as GET would return it, so one parser serves both.

Verify the signature

Deliveries follow the Standard Webhooks spec, so any off-the-shelf library verifies them:
The signature is HMAC-SHA256 over {webhook-id}.{webhook-timestamp}.{raw body} with the base64-decoded bytes after whsec_.
Verify against the raw body — re-serialising JSON changes the bytes. Reject timestamps older than a few minutes to defeat replays (the libraries do this for you).

Retries and disabling

If your endpoint doesn’t return 2xx, delivery retries on this ladder: 30s, 2m, 8m, 32m, 2h, 6h, 6h, 6h — about a day. After 10 consecutive failed attempts the endpoint is auto-disabled (status: "disabled", disabledReason: "consecutive_failures"); fix it and re-enable with PATCH /v1/webhooks/endpoints/{id} { "status": "active" }. Any delivery can be re-sent by hand: POST /v1/webhooks/deliveries/{id}/retry, or the Retry button in the dashboard.

Write idempotent handlers

The same event can arrive twice (a retry after a timeout you actually handled). Key your handler on data.id + data.status and make a repeat a no-op. Don’t infer order from arrival time — the payload’s status is the truth.

Events

Events and deliveries are browsable: GET /v1/webhooks/events, GET /v1/webhooks/deliveries?status=failed — the same log the dashboard shows.