GET endpoint, but you should never have to.
This guide is the map. It links down into the concept pages that own the authoritative field lists (Money, Virtual Accounts) and out to the endpoint reference (Payouts, Orders). It does not re-list them.
Prerequisites
- An onboarded customer. See Onboard a Customer.
- A Virtual Account on that customer. See Add Virtual Accounts.
- A webhook endpoint subscribed to the
transaction.*andorder.*events. See Webhooks.
The journey at a glance
{ "code": "USD", "amount": "100.00" }). Echo what Conduit sends you; never round-trip through a float.
Step 1 — Fund the Virtual Account
State: the account is active, balance is zero
You start with an
active Virtual Account. You reached it by submitting a VIRTUAL_ACCOUNT feature application and receiving virtual_account.activated — see Add Virtual Accounts. A non-active account (pending_activation, disabled) cannot receive a usable deposit yet. The Virtual Accounts page covers that lifecycle.Advance: send funds to the deposit instructions
Your customer sends funds to the
depositInstructions[] returned on the Virtual Account. No API call starts a deposit. Conduit detects the incoming funds and opens a deposit transaction for you.- The deposit is reversed or compliance returns it before credit →
transaction.failedwithfailureCode: RETURNED_BY_SENDER. See RETURNED_BY_SENDER. - A compliance review parks the deposit →
transaction.failedwithfailureCode: COMPLIANCE_HOLD. See COMPLIANCE_HOLD. - The sender address is unregistered → handled by the
awaiting_sender_informationpath above; SENDER_INFO_TIMEOUT is the terminal failure if it expires.
Step 2 — The balance becomes available
State: funds are credited and spendable
Once
transaction.completed fires for the deposit, the credited amount appears in the Virtual Account’s balances[].available. Each balance carries three buckets:| Bucket | Meaning |
|---|---|
available | Spendable now — this is what a payout or conversion can draw on. |
pending | Credited but not yet final; not spendable. |
frozen | Held by a compliance action; not spendable. |
Spend against
available only. Initiating a payout or order for more than the available amount is rejected at submission. The Virtual Accounts page is the source of truth for the balance shape.Step 3 — Convert the balance (optional)
Skip this step to pay out in the same asset you hold. Convert when the balance and the payout currency differ — for example, USD in a Virtual Account that you want to send out as USDC. A conversion is an Order: a firm, rate-locked exchange.State: pending, with a locked rate
POST /v2/orders creates the order in status pending and returns 202 Accepted. The response carries sourceAssetAmount, destinationAssetAmount, and a lockExpiresAt — the rate holds until then. First confirm the route is supported and learn any required recipient fields with GET /v2/orders/requirements.Advance: execute (or let it auto-execute)
A
pending order does not move funds until executed. Execute it with POST /v2/orders/:id/execute, or let Conduit execute it when source funds land if you created it with autoExecute: true. To back out before execution, call POST /v2/orders/:id/cancel.Webhooks: order.created → order.succeeded
order.created fires on creation. order.succeeded fires once the source amount is debited and the destination amount is credited and spendable. order.succeeded carries the spawned transactionId so you can reconcile against the underlying transaction. The order status moves pending → succeeded.- No live rate for the pair at creation →
POST /v2/ordersreturnsRATE_UNAVAILABLE. See RATE_UNAVAILABLE. - The lock expires before the order is executed → the expiry sweep cancels it:
order.cancelledwithreason: expired. - Execution fails terminally →
order.failedwith areasonCode:INSUFFICIENT_FUNDS(source short at execution),PROVIDER_UNAVAILABLE(transient — retry may succeed),PROVIDER_REJECTED(declined — change funding source or recipient),INTERNAL_ERROR(contact support), orCANCELLED.
Order-level failures surface on
order.failed with a reasonCode. The transaction-level failureCode vocabulary in the next step is separate. Don’t expect a conversion failure on transaction.failed.Step 4 — Initiate a payout
Now send the available balance to an external destination. A payout is awithdrawal-type transaction. The Payouts reference documents the request body and every field. This section is about the states it passes through.
State: pending
POST /v2/payouts returns 202 Accepted with the transaction in status pending. While pending, the payout clears compliance and Travel Rule exchange, and — for non-custodial wallets — awaits the customer’s signature. The reserved balance is held but not yet sent.Advance: usually nothing — Conduit drives it
A custodial payout advances on its own. You can still cancel a
pending payout with POST /v2/payouts/:id/cancel until it broadcasts. Once it reaches processing, the cancel returns 409 PAYOUT_NOT_CANCELLABLE. Track state with GET /v2/payouts/:id (or the equivalent GET /v2/transactions/:id).Webhooks: transaction.created (and signing events, if non-custodial)
transaction.created fires with type: "withdrawal". For a non-custodial wallet source, transaction.awaiting_user_signature then fires with the verificationUrl to route the customer to. Multi-signer payouts also emit transaction.signature_collected per stamp and transaction.quorum_met once enough are in. The Non-Custodial Wallets page covers that branch end to end.pending
- The recipient isn’t whitelisted for an
INTERCOMPANYpayout →422 RECIPIENT_NOT_WHITELISTEDat submission. See RECIPIENT_NOT_WHITELISTED. - A required supporting document is missing →
422 DOCUMENTATION_REQUIREDat submission. See DOCUMENTATION_REQUIRED. - The document review is declined after acceptance →
transaction.rejectedwithreasonCategory: document_inadequate(the payout readsfailed/failureCode: COMPLIANCE_REJECTED). - The customer never signs or declines (non-custodial) →
transaction.failedwithUSER_SIGNATURE_TIMEOUTorUSER_SIGNATURE_DECLINED. See USER_SIGNATURE_TIMEOUT, USER_SIGNATURE_DECLINED.
Step 5 — The payout reaches the rail
State: processing
Once compliance clears and any required signature is in, the payout hands off to the rail (bank or chain) and moves to
processing. The funds are committed. The payout can no longer be cancelled.- The source is short at settlement →
transaction.failedwithINSUFFICIENT_FUNDS_AT_SETTLE. See INSUFFICIENT_FUNDS_AT_SETTLE. - The chosen rail isn’t available →
RAIL_UNAVAILABLE; a rail-policy rule blocks it →RAIL_POLICY_REJECTED. See RAIL_UNAVAILABLE, RAIL_POLICY_REJECTED. - A crypto broadcast is declined pre-broadcast →
PROVIDER_REJECTED. See PROVIDER_REJECTED. - The counterparty VASP rejects the Travel Rule transfer →
TRAVEL_RULE_REJECTED. See TRAVEL_RULE_REJECTED.
Step 6 — Terminal state
The transaction stops at exactly one terminal state. This is where your integration takes its final action.| Terminal status | Webhook | Meaning |
|---|---|---|
completed | transaction.completed | Funds reached the destination. The settlement reference (e.g. fedwireImad, achTraceNumber, txHash) is on the relevant source/destination side of the payload. |
failed | transaction.failed (or transaction.rejected for a declined document review) | The payout could not complete. Reserved funds, if any, are released. Branch on failureCode; when it’s absent the cause isn’t actionable — contact support. |
cancelled | transaction.cancelled | Intentionally terminated (you called cancel, or a lock expired). Not a failure — there is no failureCode. cancellationReason is client_cancelled or expired. |
The customer-facing transaction status collapses to five values —
pending, processing, completed, failed, cancelled. Compliance outcomes that look distinct internally all surface as failed on the public API, differentiated by failureCode. The full failure-code catalog and its per-code recovery steps live in the Errors reference.Reacting to the terminal event
completed— store the settlement reference for reconciliation. The journey is done.failedwith an actionablefailureCode— follow the matching error playbook above; most resolve to “fix the input and submit a new payout with a freshidempotency-key.”failedwith nofailureCode— treat as terminal; contact support.cancelled— no funds moved; reserved balance is back inavailable. Submit a new payout when ready.