Core ConceptsMoney Movement

Money Movement

Deposit, conversion, payout: what happens when fiat arrives on a virtual account, and how to read each record.

The pipeline

When your customer's bank payment lands on their virtual account, three records are created in sequence, each with its own webhook.

The pipeline is durable: it survives restarts and retries without double processing, and every movement posts balanced double-entry records to an internal ledger. GET /api/balances is read straight from that ledger.

Deposits

GET /api/deposits lists money that arrived.

{
  "id": "dep_033y4KlMnOpQrStUvWxYz",
  "object": "deposit",
  "kind": "fiat",
  "rail": "ach",
  "customer_id": "cus_033y1FhTOmoW9W2jAuSvQx",
  "virtual_account_id": "va_033y2GhUPnpX0X3kBvTwRy",
  "asset": "usd",
  "amount": "100.00",
  "sender_name": "ANA SILVA",
  "sender_account": "021000021/123456789",
  "remittance_info": "invoice 42",
  "network_reference": "a1b2c3d4-5e6f-7081-92a3-b4c5d6e7f809",
  "scheme": "ach_credit",
  "status": "completed",
  "conversion_id": "cnv_033y5LmNoPqRsTuVwXyZa",
  "received_at": "2026-08-26T09:31:12+00:00",
  "created_at": "2026-08-26T09:31:12+00:00"
}

Statuses run detected, confirmed, orchestrating, completed, with returned, failed, and on_hold as the exception paths. The full state machine, the terminal markers, and the webhook fired on each transition are in Statuses & Lifecycles.

The amount is the gross the sender sent. Where a banking rail deducts its own fee before crediting, we read the arrival for the gross rather than passing that deduction to your customer.

network_reference is the bank network's end-to-end id. It is what a bank can trace, so it is what support quotes when a sender claims a payment went missing.

Conversions

GET /api/conversions lists the FX and fee event between deposit and payout.

{
  "id": "cnv_033y5LmNoPqRsTuVwXyZa",
  "object": "conversion",
  "customer_id": "cus_033y1FhTOmoW9W2jAuSvQx",
  "deposit_id": "dep_033y4KlMnOpQrStUvWxYz",
  "source_asset": "usd",
  "source_chain": null,
  "source_amount": "100.00",
  "target_asset": "usdc",
  "target_chain": "solana",
  "destination_address": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
  "destination_memo": null,
  "fee_lines": [
    {"kind": "onramp", "amount": "0.30", "asset": "usd", "collection": "monthly"},
    {"kind": "partner_fee", "amount": "1.00", "asset": "usd", "collection": "realtime"}
  ],
  "fee_total": "1.30",
  "exchange_rate": "1.00",
  "execution_mode": "prefunded",
  "status": "completed",
  "delivered_amount": "99.000000",
  "created_at": "2026-08-26T09:31:13+00:00"
}
  • exchange_rate is target per source before fees, frozen at execution, so the arithmetic is always reconstructible.
  • fee_lines itemizes exactly who charged what. Each line carries a collection: realtime lines were netted from the delivery, monthly lines are metering for your invoice and did not shrink the delivery.
  • On the standard plan our conversion fee is collected monthly, so deliveries go out whole and only your own partner_fee nets in flight. See Fees & Quotes.

Statuses run created, routing, executing, settling, completed, with failed, the refunding and refunded reversal path, and requires_attention for a conversion parked for operator review.

Payouts

GET /api/payouts lists the on-chain delivery.

{
  "id": "pay_033y6MnOpQrStUvWxYzAb",
  "object": "payout",
  "customer_id": "cus_033y1FhTOmoW9W2jAuSvQx",
  "conversion_id": "cnv_033y5LmNoPqRsTuVwXyZa",
  "kind": "crypto",
  "destination_address": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
  "asset": "usdc",
  "chain": "solana",
  "amount": "99.000000",
  "tx_hash": "5Kd9ptqWq6H5o1pVEQ4Yb9r8bYyvE7Ne1sHqW7EiT7cJ",
  "status": "completed",
  "completed_at": "2026-08-26T09:31:19+00:00",
  "created_at": "2026-08-26T09:31:15+00:00"
}

tx_hash is the on-chain proof. Link it in your UI to the chain's explorer.

The unified feed and balances

  • GET /api/transactions is one flat, deduplicated feed across the whole trail, for activity screens and reconciliation, so you do not merge three lists yourself. Filter it by customer_id.
  • GET /api/balances and GET /api/customers/{id}/balances return ledger-truth balances per customer and asset. A fully delivered deposit nets the customer's fiat balance back to zero, and your collected partner fees accumulate on your own balance.

Wallet balances are a separate thing entirely: they are chain reads, not ledger rows. See Wallets.

When inventory is short

Deliveries come from prefunded treasury inventory. If a corridor is momentarily short, the deposit waits in orchestrating and is released the instant inventory is replenished. Funds are never dropped, and there are no partial deliveries.

Sandbox treasuries start empty, which makes this the most common sandbox surprise. Call simulate/treasury_funding before your first deposit. See Sandbox Testing.