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 live 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 the envelope
id. Deliveries are at least once. Thewev_id in the body is stable across retries and replays, while a replay goes out under a different delivery id, so key your processed-ids table on the envelopeidand skip anything you have seen. - Order by
sequence, not arrival. Every event carries asequenceassigned when it is created, and a newer event always has a higher number. Retries and parallel delivery reorder arrivals, so when two events describe the same object, the highersequenceis the newer state. The numbers are not contiguous for your account: never wait for a gap to fill, and still process an event that arrives after a higher-numbered one. - Verify the signature on every delivery before trusting the payload. The recipe is in Verifying Signatures.
- 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 we emitted, newest first. To backfill, page back past the start of the outage and process anyidyour processed-ids table does not hold.POST /api/webhook_events/{webhook_event_id}/replayre-delivers one event, as often as you need.- An event whose
published_atisnullhas not gone out yet. The dashboard's Webhooks page shows the same history.
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/{customer_id}/balances |
| Wallet balances | GET /api/wallets/{wallet_id}, GET /api/wallets/total_balances |
| Wallet activity | GET /api/wallets/{wallet_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 can appear there before any webhook, so 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 in
on_holdis paused for review and needs our team to resolve it. It resolves toorchestratingor toreturned. - Wallet operations that
expiredmoved nothing and appear nowhere in the money trail. - A conversion in
requires_attentionis parked for our team, for example a delivery that failed on chain. It is not terminal, and it is worth an alert.
Month end
Your invoice's usage lines count live wallet creations, wallet sends that reached the chain, and delivered webhook events, all of which you can list. To check them, count the same objects for the period and compare. If a line does not match, write to support with the ids.