Overview
A deposit-funded order is an order you create without naming asource. Instead of pointing at a resource that already holds the funds, you tell Conduit which crypto asset the order will be funded in, and Conduit returns a crypto address to send that asset to. When the funds arrive and clear, the order executes on its own.
Use it when the money is not with Conduit yet — the end customer is about to send crypto in, and you want one call that both locks the rate and tells you exactly where the funds should go. Use a named source instead when the customer already holds a balance in a wallet or Virtual Account.
Deposit funding is crypto-only. An order funded from fiat always names its Virtual Account source explicitly.
What the customer needs first
The funding address is Conduit’s, not the customer’s, so the customer needs none of the setup a wallet of their own would take:- no
claim-non-custodialcall, - no signer roster, passkey enrollment, or signing threshold,
- no wallet of their own on any chain —
GET /v2/customers/{customerId}/walletsmay be empty, - no co-signature on the way out: the order executes on its own once the funds clear.
crypto_wallet feature active. Without it, POST /v2/orders returns 403 FEATURE_NOT_ENABLED at the point Conduit goes to resolve the funding address — the order is never created. Submit the feature the usual way (POST /v2/customers/{customerId}/features with type: "crypto_wallet") and wait for it to activate before the first order. See Add a crypto wallet for the feature request; stop after activation, and skip the claim.
This is the route for a customer who will never hold a Conduit wallet. The
feature is the entitlement to move crypto, not an instruction to provision
anything — activating it alone issues no address and asks nothing of your end
user.
Which assets can fund an order
Deposit funding covers a fixed set of asset-and-chain pairs, because Conduit has to be able to send the funds back off the address it hands you:
Any other combination is rejected at create with
400 UNSUPPORTED_ASSET naming the pair you asked for. That includes pairs the API accepts elsewhere: sourceAsset.code and sourceAsset.chain are the API-wide asset and chain enums, so the schema will let you send USDC on solana and the create call is what refuses it. Name a source explicitly to move an asset outside this set.
Register the sending address first
A funding address accepts money only from addresses the customer has registered. Register the wallet the funds will be sent from — the registration has to exist by the time the funds land, so in practice you register once, up front, and reuse it across orders:201 means the address is registered and can send; 202 means screening has not resolved yet — see below, you do not have to wait for it. See Registered addresses.
Either custody type qualifies here. self_custody and third_party both clear the funding gate — the gate asks only whether the address is registered for that customer and chain. (purpose: intercompany on a payout is the one place that additionally insists on self_custody; it has no bearing on funding.) Register a counterparty’s wallet as third_party with its originatorDetails, and those details are what Conduit screens the sender against. A self_custody registration carries no originator, because the customer is both sides.
Funds from an address that has resolved to not-registered are sent straight back to where they came from, and there is no way to attach the sender afterwards — see When a transfer is not accepted.
Registering and sending straight away is the ordinary sequence — a
202 is
not a reason to hold off. A transfer that lands while its registration is
still screening is held, not returned: it waits for the verdict and is
credited to the order if the address clears. Only an address that has actually
resolved to not-registered bounces a transfer. Given the 5-minute funding
deadline below, waiting out a slow screen before you send is the more likely
way to lose an order.returned) and holding them in Conduit’s custody (it ends frozen).
Creating one
Omitsource and send sourceAsset — the asset code plus its chain. Exactly one of the two is required; sending both, or neither, is a 400.
Both fields are required. sourceAsset is always crypto, so omitting chain is a 400 VALIDATION_ERROR on /sourceAsset/chain — there is no default chain for an asset code, and none is inferred. USDC alone does not mean USDC on Ethereum.
destination, lockSide, amount, and an optional autoPayout behave exactly as they do on an order with a named source.
The funding address
Every read of the order — the create response,GET /v2/orders, GET /v2/orders/:id, and the order.created webhook — carries a depositInstructions array. It is present only on deposit-funded orders, and it is always exactly one block:
depositInstructions, so funding instructions read the same way whatever you are funding. Send asset to address on chain before expiresAt.
How much to send: the order’s totalDebit, not sourceAsset.amount. totalDebit is what the customer pays on the source side — the amount being converted plus any fee charged in the source asset — and the order executes only once the funds cover it. Read it off the same response that gave you the address.
source is absent, by design
A deposit-funded order omits source entirely from every response — POST /v2/orders, GET /v2/orders, and GET /v2/orders/:id alike. The account behind the funding address is Conduit infrastructure, not a customer resource: GET /v2/customers/:customerId/wallets does not list it, and GET /v2/customers/:customerId/wallets/:walletId returns 404 for it. There is no id to hand you, so none is sent.
The presence of depositInstructions is the discriminator. Branch on that, not on source.
The funding deadline
lockExpiresAt on a deposit-funded order is a funding deadline, not a rate expiry, and depositInstructions[0].expiresAt always equals it.
You can still cancel a pending deposit-funded order yourself with
POST /v2/orders/:id/cancel.
Funds no order claims are sent back
Crypto that reaches a funding address without a matching order to claim it is not credited to the customer and cannot be spent. That covers every near miss: an amount that does not cover an order, funds arriving after the order expired or was cancelled, the surplus from an over-funded order, and funds sent to an address with no order behind it at all. Once no pending order is left on the address, Conduit sends those funds back on-chain to the address they came from. There is no separate waiting period: the order’s funding deadline is the only clock. A remainder worth under a dollar stays put rather than being sent back for less than it costs to move. Two consequences worth designing for:- A short send leaves the order pending, not part-filled. The order executes only once the address holds at least
totalDebit; until then it sitspendingand the clock keeps running. If the shortfall is never made up before the deadline, the order expires and the funds go back. - Re-funding needs a new order. Once an order is terminal its address no longer claims anything. Create a fresh order and read the new
depositInstructions.
Reading the deposit’s own record
A deposit into a funding address is an ordinary transaction, readable and listable onGET /v2/transactions exactly like any other deposit. When it has not failed, it carries three extra fields:
A
pending order’s entry in funded is earmarked for it; an order that has drawn the funds has actually moved them off the address, which can be well before it reports succeeded. That is why orderStatus rides on the field at all: an order that drew the funds and then failed keeps its entry and reports failed, because that money has left and does not come back. Only a claim that never drew anything is released when its order fails or is cancelled — that slice re-attributes to the next order or goes back.
On a deposit that has not failed, the three fields always add up. source.assetAmount equals the sum of every funded[].amount plus returned plus available. The transfer is filled in the order the funds left it — returned first, then each claim in funded oldest first, and whatever is left is available — so no two of the three ever count the same money. Use this to answer “did my money stick?” without a second request. A failed deposit omits all three — see below for where to look instead. A transfer into a funding address is never charged a deposit fee, so its source.assetAmount and destination.assetAmount are equal and the sum reconciles against either; a fee-bearing deposit is a bank transfer into a Virtual Account, which carries none of these three fields.
fundedBy, an array of { transactionId, amount } naming every deposit that funded it. Read it off GET /v2/orders/:id.
The return is its own transaction
Money sent back from a funding address is a second transaction,type: "deposit_return", with its own id, status, and on-chain hash. It carries returnOf, the id of the deposit it returns:
deposit_return transaction itself always looks the same regardless of how much it returns. What it means for the deposit side varies:
- A transfer that funded something first, then had a remainder returned — a partial return, or a full return of an over-funded transfer after its order settled — leaves the deposit
completed, withreturnedandavailablereflecting exactly what happened. - A transfer that never funded anything — nothing ever claimed it, or it could not be accepted at all — leaves the deposit
failed, withfunded,returnedandavailableall omitted. Thedeposit_returntransaction is still there, and itsreturnOfstill names the deposit: check it to see where the money went.
deposit_return carries no linkedOrderId: it is returned precisely because no order claimed it, and every order that did claim funds reports its own outcome on order.*.
When a funding transfer is not accepted
A transfer that is not accepted never funds anything, so the order cannot reach its total and expires at its deadline. The deposit itself ends instatus: "failed" with a neutral reason and no failureCode — and, on a failed deposit, funded, returned and available are all omitted. Check GET /v2/transactions?type=deposit_return for the matching returnOf to see where the money went; no second transaction is produced when nothing was sent back. Contact support if it persists.
There is no sender-information path here
A crypto deposit into a customer’s own wallet that arrives from a sender Conduit has not seen parks ontransaction.awaiting_sender_information and waits — you clear it by submitting the sender’s details, and the funds are credited. None of that applies to a funding address. The registration check is the whole gate:
So the fix for an unregistered sender is always before the fact: register the address, then send. After the funds have bounced, a new transfer from a registered address is the only way forward — and since the order will have expired by then, a new order too.
Sending
originator on the sandbox funding route is refused with 400 VALIDATION_ERROR for the same reason: accepting sender identity there would let an unregistered sender fund an order. See Receive crypto for the wallet-deposit path this contrasts with.
Filtering transactions at a funding address
GET /v2/transactions accepts sourceAddress — an exact match on the address that sent a transfer.
To find every deposit that funded a given order, read fundedBy off that order instead (GET /v2/orders/:id).
The funding address on transactions
When a client-visible transaction has the funding address on one of its sides, that side is adeposit_address block rather than the usual wallet block:
walletId — there is no wallet resource to look up. Treat it as its own case in your side handling; the chain travels with the asset inside assetAmount.
Errors
These are the codes specific to funding an order this way;POST /v2/orders can return any of its usual pricing and validation errors besides.
Related
Convert crypto
Wallet-to-wallet conversions, including the deposit-funded variant.
Money Movement Lifecycle
Where an order sits in the journey of a balance.
OFFRAMP orders in sandbox
Drive a deposit-funded order end to end with simulated funds.
Money
The amount shape every field on an order uses.