GuidesReconciliation

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. The wev_ 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-partner sequence. Retries and parallel delivery reorder arrivals, and applying events in sequence order 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.

  1. Deliveries retry with backoff automatically.
  2. GET /api/webhook_events lists every event with its delivery state. Page through anything newer than your last processed sequence to backfill.
  3. POST /api/webhook_events/{event_id}/replay re-delivers one event.
  4. GET /api/webhooks/portal opens 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 checkingEndpoint
Every money movementGET /api/transactions
Ledger balances for the fiat flowGET /api/balances, GET /api/customers/{id}/balances
Wallet balancesGET /api/wallets/{id}, GET /api/wallets/total_balances
Wallet activityGET /api/wallets/{id}/history
What we emittedGET /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.
  • failed means nothing moved, and a replacement is safe. returned means money moved and came back, so reconcile it as two movements and never auto-resend.
  • A deposit on_hold is paused, not lost. It resolves to orchestration or to returned, each with its webhook.
  • Wallet operations that expired moved nothing and appear nowhere in the money trail.
  • A conversion in requires_attention is 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.