- A
sourcenames the external system and carries matching defaults. - An
external_transactionis one stored row from that system, with a status:open,matched, orignored.
Create a source
default_account_id scopes matching to one account for rows that do not carry their own account_id. A name already used by an inline POST /v1/reconciliation_runs exists as an implicit source; creating it explicitly promotes it and applies your defaults.
Ingest rows
(source, external_id): a re-pushed row never mutates the stored copy and reports replayed. Invalid rows report error per item without blocking the rest. Amounts are integer minor units, sent as a JSON string (an integer is also accepted on write) and always returned as a string. Parse money with a big-integer or decimal type, never a JS number. raw retains the provider payload for audit.
reference_rail, reference_kind, and reference_value must be provided together. When the triple resolves to a transaction external ref, matching pairs that transaction’s posting first, exactly, with strategy external_ref. Write the same triple on both sides when you book the internal transaction and reconciliation stops depending on amount heuristics.
Run reconciliation per source
open rows in the window. Strategy, window, and tolerance default from the source. Matching passes run in order: external_ref, then exact, then sum_in_window when requested.
Runs with at most 500 open rows execute synchronously and return the full result. Larger runs return status: queued; a worker executes them. Poll GET /v1/reconciliation_runs/{id} or subscribe to reconciliation.run_completed. One queued or running run per source; a second request returns run_in_progress.
Work the break queue
status=open after a run is your break list, durable across runs. Three actions resolve a break:
POST /v1/external_transactions/:id/ignorewith areason: the row should never match (provider fee booked elsewhere, test row). Emitsexternal_transaction.ignored.POST /v1/external_transactions/:id/matchwithposting_ids: manual attribution. The postings must be unreconciled, share the row’s currency, and sum exactly to its amount; otherwise 422 with the residual in the hint. Emitsreconciliation.match_created.POST /v1/external_transactions/:id/unmatch: undo a match. Postings reopen, the match row is kept withreversed_atfor audit, the row returns toopen. Emitsreconciliation.match_reversed.
GET /v1/external_transactions/:id/matches is the audit trail, including reversed matches.
Hosted inbound endpoint
Instead of polling and pushing from a cron, mint a per-source push URL:inbound_url and a one-time inbound_secret. Calling again rotates both; DELETE disables. Sign every push over the exact raw body:
kind selects the payload adapter; custom is this generic scheme, and provider-specific adapters slot in per source without an API change.
Re-posting to the enable endpoint is idempotent and keeps the existing URL. Pass {"rotate": true} to mint a new token and secret, or {"provider_secret": "..."} to store a secret issued by the provider instead of a minted one; the response then carries no plaintext secret.
Cobo
Create the source withkind: "cobo" and Cobo’s webhook public key, then paste the inbound_url into the Cobo console as a webhook endpoint. No signing secret is involved; the endpoint verifies Cobo’s Ed25519 signature (BIZ-TIMESTAMP and BIZ-RESP-SIGNATURE headers) against the key.
Success, Succeeded, or Completed); pending, confirming, and failed callbacks are acknowledged and skipped, as are non-transaction events. Deposits are positive, withdrawals negative. Amounts convert from Cobo’s decimal asset units to integer minor units, and each row carries a reference triple (reference_rail is the chain, for example eth, with reference_kind: "tx_hash"), so external-ref matching works out of the box. Redeliveries of the same transaction_id report replayed.
Recipe: reconcile Stripe payouts
- Book your internal transaction with an external ref when the charge settles:
{ "rail": "stripe", "kind": "charge", "value": "ch_3NqXcd" }. - Nightly, list Stripe balance transactions and push them:
- Queue a run for the day and alert on
reconciliation.run_completedwhenunmatched_count > 0.
balance.available or charge.succeeded payloads to the inbound URL.
Next steps
Reconciliation
Inline snapshot runs and the matching model.
Webhooks
Event types and signature verification.
Pagination
Cursors on the external transactions list.
API reference
Sources and external transactions endpoints.