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_rateis target per source before fees, frozen at execution, so the arithmetic is always reconstructible.fee_linesitemizes exactly who charged what. Each line carries acollection:realtimelines were netted from the delivery,monthlylines 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_feenets 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/transactionsis one flat, deduplicated feed across the whole trail, for activity screens and reconciliation, so you do not merge three lists yourself. Filter it bycustomer_id.GET /api/balancesandGET /api/customers/{id}/balancesreturn 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.