Prerequisites
KYB approved
APPROVED. A payout attempt returns
403 COMPLIANCE_NOT_APPROVED until it does.A refund destination per settlement currency
GET /v1/refund-destinations returns one for every currency you will settle
in. Confirm the bank rail shows is_verified: true; for crypto, re-check the
address and network by eye — nothing else can.A live key with the right scopes
A registered webhook endpoint
GET /v1/webhooks/config returns your URL and has_secret: true. Without a
secret, deliveries arrive unsigned.Correctness
Idempotency keys are persisted, not generated inline
Idempotency keys are persisted, not generated inline
You read amounts rather than recomputing them
You read amounts rather than recomputing them
settlement_amount, total_fee_amount, and exchange_rate from the
payout or the quote. Never derive them from a rate and a fee percentage — a
figure that disagrees with ours becomes a reconciliation dispute.Every amount is paired with its currency
Every amount is paired with its currency
amount guarantees someone eventually reads it in
the wrong denomination.Provider codes come from the API
Provider codes come from the API
GET /v1/banks rather than shipping a hardcoded list. Codes are set
upstream and change without a version bump on our side.Beneficiary names are institution-confirmed
Beneficiary names are institution-confirmed
destination_account_name returned by /v1/banks/resolve, not
what your user typed.Webhook handling
Signatures are verified against the raw body
Signatures are verified against the raw body
Handlers acknowledge fast and process async
Handlers acknowledge fast and process async
2xx within 10 seconds. Real work goes on a queue. A slow inline handler
times out and produces duplicate deliveries.Events are deduplicated on event_id
Events are deduplicated on event_id
created_at, not on arrival order. The same event can arrive more than
once.Unknown event types are ignored, not thrown on
Unknown event types are ignored, not thrown on
Out-of-order deliveries cannot move state backwards
Out-of-order deliveries cannot move state backwards
data.status as the truth and ignore an event that would regress a record
you have already advanced.You know refund events carry a payout object
You know refund events carry a payout object
refund.* payloads have "object": "payout" and data.id is the payout ID.
Fetch amounts from GET /v1/payouts/{id}/refunds.
See Webhook events.Failure paths you must handle
These are the ones that cost money when they are missed.Reconciliation
Do not rely on webhooks alone as your source of truth. Run a periodic sweep that reads state back and repairs anything your handler missed.status_timeline on a payout is the authoritative history — every transition,
in order, with the reason. Reconcile against it rather than against webhook arrival
times.
Alerting
Operational limits
expires_at off the instruction and the quote rather than hardcoding the
first two — they are configurable per account.
Last checks
Run one real payout end to end
GET /v1/payouts/{id}.Force a failure deliberately
payout.expired by releasing the order rather than retrying forever.Confirm no key is in client code
sk_live. The key authorises payouts.Verify request IDs reach your logs
X-Request-Id and confirm it lands next to your own record. It is the
first thing support will ask for.