Composability of Transactions
Conduit's API is built to be composable. A small set of primitives, snapped together in different orders.
July 31, 2026
Most cross-border payments APIs grow by accretion. A new corridor lands, so a new endpoint lands with it. Then a new asset, so a new balance format. Then a new rail, so a new status vocabulary and a new way of telling you the money arrived. By the third corridor, the integrator is maintaining three of everything: three amount parsers, three state machines, three error-handling paths that all mean roughly "it didn't work."
Conduit's API is built the other way around. There is a small set of primitives, they all speak the same language, and every flow (onramp, offramp, pass-through, multi-corridor settlement) is those same pieces snapped together in a different order.
That's what we mean by composable, and it's worth being specific about why we’ve earned that word.
The primitives
Customer: an onboarded business. Everything else hangs off it.
Virtual Account: a fiat account with real deposit instructions. Actual bank details your customer's counterparties can wire to. Fiat-only, by design.
Crypto Wallet: an on-chain address, one per chain. Crypto-only, by design.
Deposit: money arriving. Never initiated by an API call; Conduit watches the rail, bank or chain, and opens the transaction for you.
Order: a conversion between assets. Onramp is fiat→crypto, offramp is crypto→fiat. Every order is a firm, rate-locked quote: exact source amount, exact destination amount, and a lock expiry, all before anything moves.
Payout: money leaving, to a bank rail or to a blockchain, through one endpoint.
Three things that are uniform
Composability isn't a property of a diagram. It comes from things being genuinely identical across the whole API, so that chaining them together requires no translation layer in between. Three things are identical here:
.png)
One money shape
Every amount, everywhere – request, response, webhook, error payload – looks like this:
json
{ "code": "USD", "amount": "100.50" }
Crypto amounts add a chain field. That's the only variation.
Decimal strings, never floats. No base units. No second representation that shows up in one product and not another. USD, USDC, and ETH are written the same way, so an integrator writes one money parser and it works across the entire API – including the parts they haven't integrated yet.
.png)
One lifecycle
Every transaction moves through the same five public states:
pending → processing → completed
↘ failed
↘ cancelled
A bank deposit. A crypto deposit. A conversion. A wire out. An on-chain payout. Same five states. You learn the state machine once, every product reads the same way.
Notably, we don't invent new statuses per product to express new kinds of failure. Failures are differentiated by a documented failureCode vocabulary, which means the integrator's error handling stays a single switch statement instead of fanning out into per-product special cases.
One integration pattern: submit and listen
Make one API call, then react to webhooks. No polling loops, no status-check cron jobs.
Deposits, conversions, payouts, onboarding decisions, signature ceremonies – all of it arrives as webhook events with a consistent envelope, echoing the clientReferenceId you supplied so every event correlates back to a record in your own system.
Because the pieces are uniform in shape, lifecycle, and delivery, they chain without glue code. That's the composability claim, and it's checkable against the reference docs rather than something you have to take on faith.
.png)
The compositions
Here's what falls out. Each of these is the same primitives in a different order, rather than a bespoke product per corridor:
Collect and hold. A counterparty wires USD to a Virtual Account. The balance becomes available. Done. One primitive, no orchestration.
Fiat → stablecoin. Fund a Virtual Account in USD, create an onramp order, and USDC lands in the wallet, spendable. Orders support autoExecute, so "convert whatever arrives" is one flag: the order executes itself the moment the funding deposit settles. That's composition expressed in a single field.
Stablecoin → fiat. Receive USDC on-chain, create an offramp order, USD becomes available, pay out over Fedwire or ACH to a bank account. This is the cross-border settlement story in full: money enters as crypto in one place and exits as local fiat in another, and the integrator wrote four calls they already understood individually.
Pass-through payout. Deposit in, payout out, same asset. Skip the order step entirely. Optional steps are genuinely optional – the chain doesn't break when you omit one.
Payouts: one door, many rails
POST /v2/payouts is the single exit for money leaving the platform. The destination object discriminates: fiat, with a rail such as FEDWIRE, or crypto, with a chain and an address.
Same request shape. Same idempotency rules. Same lifecycle. Same failure vocabulary whether the money leaves over a bank wire or a blockchain.
Settlement references come back rail-appropriate (fedwireImad, achTraceNumber, txHash) on the same payload shape, so adding a rail is simply a field you read.
Balances compose too
Every balance, on a Virtual Account or a wallet, has the same three buckets:
- available — spendable now
- pending — credited, not yet final
- frozen — compliance hold
This is one mental model for "where is my money," across fiat and crypto, with no reconciliation between two different accounting vocabularies.
Custody is a layer, not a fork
This one is subtle and it's the part engineers tend to push on hardest, so it's worth stating plainly: custody model composes orthogonally with everything above. It does not split the API into two products.
Receiving money is identical for custodial and self-custody wallets. Same address, same deposit lifecycle, same webhooks.
Custody only changes who signs when money leaves. Self-custody customers hold their own signing material as passkeys. A payout from a self-custody wallet parks at a signature gate, the customer's signers approve on a Conduit-hosted verify page, and there is no code path in which Conduit moves those funds alone. That's a structural guarantee you can verify in the API surface, not a policy promise in a contract.
Multi-signer builds on the same seam: M-of-N thresholds, per-wallet overrides, a progress webhook per signature collected, and a quorum-met event. It's an approval-policy layer snapped onto the existing payout lifecycle. The payout status is still just pending until it isn't.
.png)
Compliance is composed in, not bolted on
Screening, Travel Rule information exchange, and supporting-document requirements all surface inside the transaction lifecycle rather than as a separate system with its own dashboard and its own vocabulary.
A crypto deposit from an unknown sender doesn't disappear into a review queue. It parks at a named state – awaiting_sender_information - with a deadline, and there's an API call to resolve it: submit the sender's details, and optionally register the address so future deposits from it clear automatically.
Payouts that need supporting documentation fail fast at submission with a specific, documented error. Fix and resubmit.
Compliance outcomes surface as ordinary terminal states with failure codes. Which means, again, one switch statement instead of five subsystems.
The reliability primitives underneath
- Idempotency keys are required on every money-moving call. Retries can't double-send money.
- Read-only and read-write API keys are separate. A reporting integration physically cannot move funds.
- Signed webhooks and correlation IDs on every response.
- A full sandbox with the same API surface as production. Sandbox key on day one, before any onboarding paperwork.
Why this matters commercially, not just architecturally
The practical consequence of uniformity is that integration cost stops scaling with the number of corridors and assets you support.
The team that shipped a USD onramp already knows how to ship an offramp. The team that pays out over Fedwire already knows how to pay out on Solana. Adding a corridor becomes a configuration question your product team can answer, instead of a quarter of engineering work.
You learn the primitives once. After that, you're composing.
[Start building now]

.jpg)


.jpeg)
