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 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. The wev_ 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 envelope id and skip anything you have seen.
  • Order by sequence, not arrival. Every event carries a sequence assigned 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 higher sequence is 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.

  1. Deliveries retry with backoff automatically.
  2. GET /api/webhook_events lists every event we emitted, newest first. To backfill, page back past the start of the outage and process any id your processed-ids table does not hold.
  3. POST /api/webhook_events/{webhook_event_id}/replay re-delivers one event, as often as you need.
  4. An event whose published_at is null has 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 checkingEndpoint
Every money movementGET /api/transactions
Ledger balances for the fiat flowGET /api/balances, GET /api/customers/{customer_id}/balances
Wallet balancesGET /api/wallets/{wallet_id}, GET /api/wallets/total_balances
Wallet activityGET /api/wallets/{wallet_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 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.
  • 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 in on_hold is paused for review and needs our team to resolve it. It resolves to orchestrating or to returned.
  • Wallet operations that expired moved nothing and appear nowhere in the money trail.
  • A conversion in requires_attention is 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.