Skip to main content
A conversion exchanges one crypto asset for another between two of a customer’s wallets. It’s one flow regardless of what changes:
  • a different asset on the same chain — e.g. USDC → USDT on Ethereum,
  • the same asset on a different chain — e.g. USDC on Ethereum → USDC on Base,
  • a different asset and a different chain — e.g. USDC on Ethereum → USDT on Tron.
All of them use the same endpoint (POST /v2/orders) and surface the same public type: "conversion". A conversion only converts — to send funds to an external address, use a payout.
Test this flow in sandbox. Drive it end-to-end with simulated money and deterministic controls — start with the sandbox quickstart, then conversion simulation for this flow, and the cheat sheet for every magic value and simulate endpoint.

Prerequisites

  • An active customer with the crypto-wallet feature.
  • A source wallet holding the asset you’re converting from, and a destination wallet for the asset and chain you want. A non-custodial source wallet on an EVM chain collects your signers’ approvals first — the conversion stays pending, a transaction.awaiting_signature webhook delivers the signing link, and the transfer proceeds once the signing quorum approves (the same flow as a non-custodial payout). The signing link applies to a wallet in the passkey signing mode; a wallet in a programmatic signing mode instead carries a signingRequestId on that webhook — see Machine-signer stamping. A non-custodial solana or tron source wallet follows the same signing flow. A non-custodial stellar source is not supported yet and returns NON_CUSTODIAL_SOURCE_NOT_SUPPORTED.
  • Your API key.

Step 1 — Check recipient requirements (optional)

/v2/orders/requirements tells you what recipient details a route needs. A wallet destination needs none — it returns destinationRecipient: "none":
This validates the asset codes only; it does not check the specific chains. The authoritative route check is previewing the order (Step 2) — a route that isn’t enabled for your organization returns 422 UNSUPPORTED_PAIR there.

Step 2 — Preview the order

Create with autoExecute: false to lock a rate and read the quote without committing. destination is always a wallet with its own asset (code + chain). source is optional: name a wallet to convert a balance the customer already holds, or omit it and send sourceAsset to have Conduit issue a funding address instead (Step 2b below). Naming the source:
The response carries type: "conversion", status: "pending", a lockExpiresAt, and the quote in rate (referenceRate, totalSpreadBps, endUserRate — all strings; endUserRate already includes the spread). Read destinationAsset.amount to see what the customer receives. When Conduit invoices its conversion fee to you monthly, totalSpreadBps carries no Conduit take rate and fees carries no Conduit fee: the fee is on your invoice only.
Previewing this way creates a real order, with a resource commitment — that’s the point of Step 2. To price a move with no resource commitment and no resource ids first, use POST /v2/quotes instead, then create the order from the option you picked. The option lasts about 5 minutes; the order that redeems it holds its rate for 2 hours from creation. lockSide: "source" fixes what’s spent and shows what’s received; lockSide: "destination" fixes what’s received. Cancel a previewed order any time before execution starts with POST /v2/orders/{orderId}/cancel. When the source is a non-custodial wallet, the cancel keeps working after execution starts, for as long as the source transfer is still waiting to be signed. Once that transfer enters its send step, the order stays pending and the cancel returns 409.

Step 2b — Convert crypto that hasn’t arrived yet

When the customer doesn’t hold the source asset yet, omit source and send sourceAsset instead. Conduit returns an address to send the funds to, and the order executes on its own once they clear:
The response carries depositInstructions[0].address — where to send the funds — and no source at all. Send the order’s totalDebit to that address before lockExpiresAt (a funding deadline here, not a rate expiry). Omit autoExecute: these orders always auto-execute, and sending the field is a 400. Skip Step 3 (there is nothing to execute) and go straight to Step 4. Read Deposit-Funded Orders before you build on this — funds that no order claims are sent back to the sender, and Conduit may change the address at any time, so never cache it per customer.

Step 3 — Execute

To skip Step 3 entirely, pass autoExecute: true on create instead. If the source already has enough balance, execution starts right away; if not, order creation still succeeds (creating an order never checks or reserves the source balance) and Conduit automatically starts execution as soon as the available balance covers totalDebit (the locked amount on a source-locked order; the principal plus fees on a destination-locked order — see How much an order debits) — no polling or manual /execute call needed. Note that orders on the same source resource execute oldest-first: a still-underfunded older order blocks a newer, already-funded one until it’s funded, cancelled, or expires. An older order whose lockExpiresAt has passed and that the balance cannot cover no longer blocks a younger one; it is cancelled at the same time. Either way execution must start before lockExpiresAt or the order cancels with cancellationReason: "expired"; funding after that point doesn’t revive it, and you’d need a new order. One exception, on a deposit-funded order only: a transfer that reached the funding address before the deadline meets it, so the order stays pending past lockExpiresAt while that transfer is checked, then executes if it clears (see Deposit-funded orders).

Step 4 — Settle

The order settles once the on-chain transfers confirm. Watch the webhooks below to learn when it settles; poll GET /v2/orders/{orderId} until status is succeeded (terminal) as a fallback. If your endpoint was down long enough for a delivery to exhaust its retries, list GET /v2/webhooks/deliveries?status=failed and retry those to reconcile. A conversion from a non-custodial source settles once its signing quorum approves on the signing page or with a machine stamp, and its on-chain transfers reach finality. Order status is lowercase pending | succeeded | failed | cancelled. Some conversions settle through a conversion partner. For those orders the source funds leave the customer’s wallet (offramp) or virtual account (onramp) in a separate withdrawal: a transaction with data.type: "withdrawal" and linkedOrderId set to the order, which emits the usual transaction.* events. You can’t cancel that withdrawal. If it fails before the funds leave, the order fails and the source is released; once it completes, the order succeeds when the converted funds reach the destination. If the partner fails after that, it returns the funds as a deposit transaction into the source wallet or virtual account. The order stays pending until Conduit confirms the refund, then fails with reasonCode: "provider_rejected". In the sandbox, orders/:id/simulate/conversion-failed makes the partner fail and return the funds, and the order fails without the wait. A conversion partner prices the orders it settles, in the same shape as a Conduit price. sourceAsset is the amount that converts. Every charge the partner takes, including the banking and network fees, is one entry in fees with owner: "conduit", and totalDebit is sourceAsset plus the fees. The rate is the partner’s rate, and a markupBps you declare is its only spread. Your margin is always taken on the fiat side: on an offramp from the partner’s payout, and on an onramp from the customer’s USD, so the partner converts only its own amount. A quote option that a partner priced can be redeemed only while the same partner still serves the customer; otherwise order creation answers 409 QUOTE_PROVIDER_UNAVAILABLE, and you request a new quote. To quote an onramp to an external_wallet, set destination.type to external_wallet and destination.custody to the recipient’s custody: a partner prices only a wallet the customer holds itself. A payout below the partner’s minimum after its fees answers 422 AMOUNT_OUT_OF_RANGE. On an onramp the partner converts at its rate when the fiat arrives, so the order’s destinationAsset amount is the partner’s estimate. The USDC arrives in the customer’s wallet as a deposit transaction that carries the exact amount; the order and its conversion keep the estimate.

Webhooks

The conversion surfaces on both the order and the transaction topics; the transaction events carry data.type: "conversion".

Errors

See also