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 survives restarts and retries without processing a deposit
twice. Every movement posts balanced double-entry records to our ledger,
which is what GET /api/balances reads.
Deposits
GET /api/deposits lists money that arrived. Filter it by
virtual_account_id for one account's history.
{
"id": "dep_033y4KlMnOpQrStUvWxYz",
"object": "deposit",
"kind": "fiat",
"payment_rail": "ach",
"customer_id": "cus_033y1FhTOmoW9W2jAuSvQx",
"virtual_account_id": "va_033y2GhUPnpX0X3kBvTwRy",
"currency": "usd",
"amount": "100",
"sender_name": "ANA SILVA",
"sender_account": "021000021/123456789",
"remittance_info": "invoice 42",
"network_reference": null,
"scheme": "ach",
"status": "completed",
"conversion_id": "cnv_033y5LmNoPqRsTuVwXyZa",
"received_at": "2026-08-26T09:31:12+00:00",
"created_at": "2026-08-26T09:31:12+00:00"
}
payment_rail is the rail the money arrived on, which can differ from
the account's primary rail, and scheme the network scheme: ach,
fedwire (a wire arrival), or rtp.
Statuses run detected, orchestrating, completed, with returned 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 trace reference (for example a
wire's IMAD) when the bank provides one, and null otherwise. It is what
a bank can trace, so it is what support quotes when a sender claims a
payment went missing. When it is null, quote the deposit id.
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",
"target_asset": "usdc",
"target_chain": "solana",
"network": "mainnet",
"destination_address": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
"destination_memo": null,
"fee_lines": [
{"kind": "onramp", "amount": "0.3", "asset": "usd", "collection": "monthly"},
{"kind": "partner_fee", "amount": "1", "asset": "usd", "collection": "realtime"}
],
"fee_total": "1.3",
"exchange_rate": "1",
"execution_mode": "prefunded",
"status": "completed",
"delivered_amount": "99",
"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.
Conversion statuses, including requires_attention, are in
Statuses & Lifecycles.
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",
"network": "mainnet",
"amount": "99",
"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 feed across the whole trail, for activity screens and reconciliation, so you do not merge three lists yourself. Filter it bycustomer_id.GET /api/customers/{customer_id}/balancesreturns one customer's ledger balances by asset and chain.GET /api/balancessums them across all your customers. 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 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 as
soon as inventory is replenished. Funds are never dropped, and there are no
partial deliveries. The wait is bounded at one hour: a deposit still
unfunded after that stays in orchestrating for our team to resolve.
Sandbox treasuries start empty, which makes this the most common sandbox
surprise. Call POST /api/sandbox/treasury/deposits before your first
deposit. See Sandbox Testing.