Reconciliation
Consume webhooks so nothing is missed or double-processed, and reconcile against the API as the source of truth.
Webhooks tell you when something happened. The API tells you what is true. A production integration uses both, webhooks to react and queries to reconcile, and never rebuilds balances from its own event replay.
Consuming webhooks safely
- Acknowledge fast, process async. Return 2xx as soon as the payload is stored, and do the work off the request path. Slow handlers cause retries, and retries cause duplicates.
- Deduplicate by event
id. Deliveries are at least once. Thewev_id is stable across retries and replays, so keep a processed-ids table and skip anything you have seen. - Order by
sequence, not arrival. Every event carries a per-partnersequence. Retries and parallel delivery reorder arrivals, and applying events insequenceorder makes your projection deterministic. - Verify the signature on every delivery before trusting the payload. The recipe is in Webhooks.
- React to the event, then read the object. The payload is a snapshot. If your handler makes decisions, GET the resource for current state rather than acting on a possibly stale copy.
When deliveries are missed
Your endpoint being down does not lose events. It delays them.
- Deliveries retry with backoff automatically.
GET /api/webhook_eventslists every event with its delivery state. Page through anything newer than your last processedsequenceto backfill.POST /api/webhook_events/{event_id}/replayre-delivers one event.GET /api/webhooks/portalopens the delivery portal: every attempt, response codes, response bodies, and manual retry. It is the first place to look when a webhook "never arrived".
Reconciling against the API
Run a periodic job, daily is typical, that compares your records to ours.
| What you are checking | Endpoint |
|---|---|
| Every money movement | GET /api/transactions |
| Ledger balances for the fiat flow | GET /api/balances, GET /api/customers/{id}/balances |
| Wallet balances | GET /api/wallets/{id}, GET /api/wallets/total_balances |
| Wallet activity | GET /api/wallets/{id}/history |
| What we emitted | GET /api/webhook_events |
Diff GET /api/transactions against your own records by id, and
investigate anything present on one side only. Fees itemize on each
conversion's fee_lines, and monthly-collected lines appear on your
invoice rather than in the flow. See
Fees & Quotes.
Wallet balances are chain reads. Funds sent straight to an address appear there with no webhook necessarily preceding them, which is exactly why reconciliation cannot rely on events alone.
If your projection and the API disagree, the API wins. Fix the projection, usually a missed or double-applied event. Never adjust your books to match a replay.
Interpreting states while reconciling
- Statuses only move forward, so your projection can treat each transition as monotonic. The full state machines are in Statuses & Lifecycles.
failedmeans nothing moved, and a replacement is safe.returnedmeans money moved and came back, so reconcile it as two movements and never auto-resend.- A deposit
on_holdis paused, not lost. It resolves to orchestration or toreturned, each with its webhook. - Wallet operations that
expiredmoved nothing and appear nowhere in the money trail. - A conversion in
requires_attentionis parked for an operator. It is not terminal, and it is worth an alert.
Month end
Your invoice's usage and gas lines are metered from the same objects you can list: wallet operations, wallet history, and webhook events. So a month-end self-check is to count your own live sends and deliveries for the period and compare against the invoice lines. Anything surprising, write to support with the ids.