simulate/* endpoints.
This page walks the full OFFRAMP lifecycle — crypto-in on a customer wallet, FX conversion, fiat-out payout — end to end. The conversion legs are internal movements that the mock provider auto-finalizes; the only external-actor step you drive manually is the customer’s inbound crypto deposit. Because the conversion legs are internal and have no real external actor, there are no per-leg failure injection endpoints for them — use orders/:id/simulate/conversion-failed to force the whole order to a failed terminal state, or arm destination payout failures at create-time via the autoPayout.recipient.bankName magic values or the accountNumber suffix protocol described below.
Prerequisites
- An
ACTIVEcustomer with a sandbox API key. If you haven’t onboarded one yet, run Sandbox quickstart first. - A crypto wallet for the customer holding the source asset (e.g. USDC on Ethereum). See Deposits for injecting a synthetic balance.
- Base URL:
https://api.sandbox.conduit.financial. Export your key:
Lifecycle overview
An OFFRAMP order moves crypto from a customer wallet to a fiat destination through these stages:- Crypto deposit detected — the customer’s inbound crypto is received on their wallet. In sandbox you inject this with the
/wallets/:walletId/deposits/simulateendpoint (the customer’s wallet sending in is a real external-actor step). - Conversion auto-finalizes — the source and destination conversion legs are internal movements. The mock provider finalizes them automatically; no manual settle calls are required.
order.succeededfires — the order reachessucceededwithin seconds of the deposit being detected.
pending after creation and reaches succeeded or failed as the legs settle. Intermediate state is observable only via GET /v2/orders/:id (poll). Terminal status surfaces via webhook.
Full happy path
Step A — Create the OFFRAMP order
202 Accepted. Capture id as ORDER_ID. The order is in pending. No webhook fires at creation time — intermediate state is polled via GET /v2/orders/:id.
Step B — Inject a synthetic crypto deposit
The customer’s inbound crypto deposit is the only external-actor step in the OFFRAMP flow. Inject a synthetic deposit following the same pattern documented at Deposits:order.succeeded fires within seconds — no further simulate calls are needed.
Poll GET /v2/orders/$ORDER_ID to observe progress if needed.
Compliance failure on the destination payout
SetautoPayout.recipient.bankName to one of the magic values below at order-creation time. The conversion completes normally — the OFFRAMP order reaches succeeded. The AML check fires on the chained Withdrawal transaction that the order spawns to deliver the fiat payout; that chained transaction fails with the matching failureCode. Integrators have to listen for both halves: order.succeeded on the order followed by transaction.failed on the chained payout.
bankName and inject the synthetic crypto deposit (Step B above). The conversion auto-finalizes (order.succeeded) and the OFFRAMP order spawns its chained payout Withdrawal (transaction.created carries linkedOrderId pointing back at the parent order id). The compliance gate fires on the chained Withdrawal and it terminates as transaction.failed.
bankName magic value | failureCode on the chained Withdrawal transaction.failed |
|---|---|
SANDBOX_AML_REJECTED | AML_REJECTED |
SANDBOX_AML_SANCTIONED | AML_REJECTED † |
bankName takes the happy path.
† On withdrawals (including the chained payout that a successful OFFRAMP order spawns), both REJECTED and sanctions classifications surface the same public AML_REJECTED failure code. The underlying classification is recorded for audit.
Destination payout rail failure
To test a fiat rail rejection on the chained payout (after a successful conversion), setautoPayout.recipient.accountNumber to a value ending in the following suffixes at order-create time. The conversion completes normally; the chained payout then fails with the corresponding failureCode on a separate transaction.failed webhook for the Withdrawal transaction.
accountNumber last 8 digits | failureCode on the Withdrawal |
|---|---|
94009001 | RAIL_POLICY_REJECTED |
94009002 | INSUFFICIENT_FUNDS_AT_SETTLE |
94009003 | RAIL_UNAVAILABLE |
94009004 | RAIL_UNAVAILABLE (rail-provider timeout — internal PROVIDER_TIMEOUT distinction preserved on internal logs; the public surface emits the same RAIL_UNAVAILABLE by design — integration retry semantics are identical for either cause) |
accountNumber, inject the synthetic deposit (Step B), and observe the OFFRAMP order complete followed by a transaction.failed on the chained Withdrawal. No mid-flow simulate call is needed.
Rate lock expiry
To test what happens when the rate lock window expires before the order is executed:200 OK returning the order at its current state. The rate lock timestamp is backdated so the next sweep treats the order as expired. Webhook: order.cancelled with cancellationReason: "expired". Semantics are identical to the ONRAMP variant; see ONRAMP orders for a worked example.
The order reaches
cancelled (expired) within about 3 seconds via an immediate background sweep tick. No polling backoff needed; no need to wait for the scheduled 30-second sweep.Conversion failure
To force the entire order to a failed terminal state, callorders/:id/simulate/conversion-failed at any point after the order is created. The endpoint is timing-independent: if the conversion has not yet started, the failure is armed and applied as soon as it does; if it is already in flight, it is aborted regardless of which leg the order is on:
200 OK returning the order at its current state. Webhook: order.failed carrying orderId, customerId, clientReferenceId, reasonCode, failureMessage (the reason you supplied), and failedAt. See Conversions for the full endpoint reference.
Order lifecycle states
| Status | Meaning | Next transitions |
|---|---|---|
pending | Order created; rate locked; awaiting execution | succeeded, failed, cancelled |
succeeded | Both conversion legs settled; crypto debited, fiat credited to the customer’s virtual account. The chained Withdrawal that delivers the fiat payout is tracked separately as a transaction.* event stream. | Terminal |
failed | A conversion leg failed (e.g. via orders/:id/simulate/conversion-failed). Destination-payout failures — AML, rail — do not put the order in failed; they surface on the chained Withdrawal’s transaction.failed. | Terminal |
cancelled | Order cancelled by client or expired before execution | Terminal |
Intermediate steps (source settled, conversion in progress) are not reflected as order statuses and do not emit webhooks. Poll
GET /v2/orders/{orderId} for current state; only the terminal outcomes surface via webhook.Webhook events
| Event | When it fires |
|---|---|
order.created | Order accepted, rate locked. |
order.succeeded | Order reached terminal succeeded state: crypto debited, conversion completed. The chained Withdrawal that delivers the fiat payout is a separate transaction. |
order.failed | Order reached terminal failed state (e.g. orders/:id/simulate/conversion-failed). Payload carries reasonCode (INSUFFICIENT_FUNDS / PROVIDER_UNAVAILABLE / PROVIDER_REJECTED / INTERNAL_ERROR / CANCELLED). AML / rail failures on the destination payout do not surface here — they surface on transaction.failed for the chained Withdrawal. |
order.cancelled | Order cancelled (expired or client-cancelled). Payload carries cancellationReason. |
transaction.created | A chained Withdrawal is dispatched to deliver the fiat payout. The payload carries linkedOrderId pointing back at the parent OFFRAMP order. |
transaction.failed | The chained Withdrawal failed (compliance reject, rail failure). Payload carries failureCode. |
The forward direction is also available:
GET /v2/orders/:id returns linkedTransactionIds: string[] — every transaction the order has spawned so far. The array starts empty and grows as the workflow progresses; it stays in sync across /cancel, /execute, and subsequent GETs. Use it when you have an order id and need to fan out to every transaction it produced without scanning a webhook log.Errors
See Errors for the full error shape.failureCode values your integration should branch on when the OFFRAMP destination payout fails — these surface on the chained Withdrawal’s transaction.failed event, not on the OFFRAMP order:
AML_REJECTED— the payout recipient failed the compliance check; not retryable without investigation.RAIL_POLICY_REJECTED/INSUFFICIENT_FUNDS_AT_SETTLE/RAIL_UNAVAILABLE— payment-rail failures; adjust amount, recipient, or rail and retry.
orders/:id/simulate/conversion-failed), the failure surfaces on order.failed with reasonCode.
Sequence diagram
Related pages
ONRAMP orders
Fiat-in to crypto-out — the mirror flow
Deposits
Injecting synthetic crypto balances into a customer wallet
Conversions
FX conversion leg mechanics and failure scenarios
Errors
Full error catalog and failureCode reference