POST, each time something in your account changes: a payout is paid or returned, a payee becomes active, a deposit is credited.
Register an endpoint
Response
- The URL must use HTTPS and resolve to a public address. IP literals and hosts that resolve to private, loopback or link-local ranges are rejected, as is a URL already used by another of your endpoints.
- Subscribe to the event types your integration handles.
"events": ["*"]subscribes to every type, including types added later. - An account can have up to 5 endpoints. Each event is delivered to every enabled endpoint subscribed to its type.
The event object
Every delivery’s body is an event:data.object is the complete object as it was when the event occurred, in the same shape as the corresponding GET endpoint returns, so most handlers don’t need to call back. The prefix of type identifies the object: payout, batch, payee or deposit. created_at is when the change was committed, not when the event was delivered. environment is live or sandbox.
Verify signatures
Your endpoint is a public URL, so anyone can send requests to it. Verify every event’s signature before acting on it, so that a forged request can’t trigger a fulfilment, a refund or a ledger entry. Infinity signs events with the open Standard Webhooks scheme. Each request carries three headers:
The official Standard Webhooks libraries verify the signature and reject timestamps more than 5 minutes from your clock, which protects against replayed requests. Always verify against the raw request body, before JSON parsing; re-serialised JSON won’t match the signature.
Verifying without a library
Verifying without a library
- Strip the
whsec_prefix from the secret and base64-decode the remainder; that’s the HMAC key. - Build the signed content:
{webhook-id}.{webhook-timestamp}.{raw body}. - Compute HMAC-SHA256 over the signed content with the key, and base64-encode the result.
- Compare it, using a constant-time comparison, with each
v1,entry inwebhook-signature. The header can carry several space-separated signatures. - Reject the request if
webhook-timestampis more than 5 minutes from your current time.
Delivery and retries
- Success is any
2xxresponse within 10 seconds. Redirects aren’t followed, so a3xxcounts as a failure, as do timeouts and connection errors. - Retries. A failed delivery is retried 5 times, after 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours, about 9 hours in total. After that the delivery is abandoned, and the event remains available through the events API.
- Signatures are computed per attempt, so
webhook-timestampandwebhook-signaturechange between retries whilewebhook-idstays the same. - Disabled and deleted endpoints receive no new deliveries, and pending retries to them are dropped.
Process events reliably
- Acknowledge quickly. Return
2xxas soon as the signature is verified and the event is queued, and do the work asynchronously. Slow handlers cause timeouts, which cause retries. - Handle duplicates. Delivery is at-least-once, so the same event can arrive more than once. Record processed
webhook-idvalues and skip repeats. - Don’t depend on order. Events aren’t guaranteed to arrive in the order they were created;
payout.paidcan arrive beforepayout.processing. Use the object’sstatusto decide what to do, and comparecreated_atonly to avoid applying an older snapshot over a newer one. - Ignore unknown types. New event types can be added at any time. Acknowledge them with
2xxand move on.
Recover missed events
Every event is retained for 30 days, whether or not it was delivered and whether or not any endpoint subscribed to it:GET /events/{id} returns a single event; its data.object is the snapshot taken when the event occurred, not the object’s current state.
Event types
An object’s creation emits only its.created event; the object’s status in that event tells you where it starts. Each later status change emits <object>.<new status>.
Manage endpoints
Test in the sandbox
Sandbox events carry"environment": "sandbox". To exercise failure handling, force an outcome with the advance helper; for example, move a paid payout to returned: