outcome field on the deposit simulate body — or by sandbox-only simulate/* endpoints. See Sandbox overview for the full posture.
This page covers three deposit types, one per fundable resource:
- Fiat deposits land on a customer’s virtual account via a simulate endpoint. The balance is credited immediately; no bank transfer occurs.
- Crypto deposits land on a customer’s wallet via a simulate endpoint. The deposit enters the same ingestion pipeline as a real provider webhook, including compliance screening and the sender-information gate.
- Deposit-funded order transfers land on the funding address the order publishes. The endpoint answers with the order, not a deposit — but the transfer itself is still an ordinary
deposittransaction you can read back. Only the section below applies to them — see Deposit-funded orders for the concept.
Prerequisites
- A sandbox API key for an
activecustomer. SetSANDBOX_API_KEY,CUSTOMER_ID,VA_ID(virtual account), andWALLET_IDin your shell. - For fiat: the virtual account must be
activefor assetUSD. - For crypto: the wallet must be
activeand have a deposit address. - Base URL:
https://api.sandbox.conduit.financial.
The
idempotency-key header is required on every money-moving POST. The
cache TTL is 300 seconds — replay the same key within that window to safely
retry; use a fresh key for a new operation.Fiat deposit — happy path
Inject a synthetic USD deposit into a customer’s virtual account.POST https://api.sandbox.conduit.financial/v2/sandbox/customers/{customerId}/virtual-accounts/{virtualAccountId}/deposits/simulate
Request body:
202 Accepted means the deposit was handed to the ingestion pipeline. Ingestion is asynchronous — exactly as it is in production, where no endpoint creates a deposit synchronously — so the response body carries only externalReference: the reference the deposit is detected under, either the one you sent or a synthetic sandbox_… one when you omitted it. Send it back verbatim: surrounding whitespace is trimmed, so the acknowledged reference — not your original string — is the one the deposit is stored under and the one the filter matches. Observe the deposit through the transaction.created webhook, or read it back with GET /v2/transactions?type=deposit&externalReference=…. Re-sending the same externalReference re-acknowledges the existing deposit rather than creating a duplicate. The customer’s USD balance updates within a few seconds, and your webhook endpoint receives transaction.completed.
Fiat deposit — compliance failure paths
Fiat deposits use the same suffix protocol as crypto deposits, matched against the last 8 digits of the sender’s bank account number (non-digit characters stripped). Pass asenderInfo.accountNumber ending in a documented suffix to force a specific compliance outcome.
The deposit-specific suffix values are listed in the Suffix catalog below. For the consolidated suffix table across deposits and withdrawals, see Scenario suffixes.
Crypto deposit — happy path
Inject a synthetic on-chain deposit into a customer’s wallet.POST https://api.sandbox.conduit.financial/v2/sandbox/customers/{customerId}/wallets/{walletId}/deposits/simulate
Request body:
202 Accepted means the deposit was handed to the ingestion pipeline. Ingestion is asynchronous — exactly as it is in production, where no endpoint creates a deposit synchronously — so the response body carries only chain and txHash: the identity the deposit is detected under, either the hash you sent or a synthetic sandbox_… one when you omitted it. Poll with the hash the response gave you, not the one you typed — on Ethereum and the other hex-hash chains it comes back lowercased, matching how the deposit is stored. Observe the deposit through the transaction.created webhook, or read it back with GET /v2/transactions?type=deposit&txHash=…. A hash identifies a transaction, not a row: reuse one across several wallets and the filter returns a deposit per credited wallet, so match destination.walletId when you do that. Compliance screens the source address (the default sandbox originator clears automatically), the balance updates within a few seconds, and your webhook endpoint receives transaction.completed.
To force a specific compliance outcome, pass a sourceAddress whose last 8 hex characters match one of the suffixes in the Suffix catalog below.
Deposit-funded order transfer
A deposit-funded order publishes a Conduit-managed funding address indepositInstructions instead of debiting a wallet you named. That address has no client-visible wallet id — Conduit owns it and may rotate it — so the order id is the handle you fund it with:
POST https://api.sandbox.conduit.financial/v2/sandbox/orders/{orderId}/deposits/simulate
Register the sending address first. A funding address accepts money only from an address the customer has registered:
201), except for the addresses documented at Registered addresses that park for review. Then pass that address as sourceAddress when you fund the order.
What differs from the wallet endpoint:
sourceAddressdecides which side of the registration gate you exercise. Pass a registered address and the funds fund the order. Pass — or default to — an address that is not registered and the funds are sent straight back: the order itself never moves, but the transfer is still a readabledeposittransaction (it readsfailed, since nothing was ever credited) and the return is a readabledeposit_returnnaming it.sourceAddress: nullis refused with400 VALIDATION_ERROR, because a transfer with no sender has nowhere to go back to.- The order’s compliance scenarios are keyed on the order, not a deposit id.
outcome: "frozen" | "returned"still works on the body, but a transfer held for review is resolved withPOST https://api.sandbox.conduit.financial/v2/sandbox/orders/{orderId}/deposits/simulate/compliance-decision(below), not the transaction-keyed lever — there is no transaction id to key on. - The sender-information gate does not apply, and
originatoris rejected. Registration replaces it: whether the sending address is registered is the only gate, and sender identity does not open it. Passingoriginatorreturns400 VALIDATION_ERRORrather than being silently ignored — on the wallet route it clears the sender-information gate, and accepting it here would let an unregistered sender fund the order.DE5E11F1, the 10,000-unit amount driver, the reserved originator names, andsimulate/sender-infoare all wallet-deposit features with no effect on this route. - Send the order’s
totalDebit, notsourceAsset.amount— the order executes only once the funds cover the full debit including fees. Send less and it stayspending; send more and the surplus goes back to the sender once the order settles. - It accepts the order in any status, expired and cancelled included. Funding an address after its order lapsed is how an unclaimed transfer arises in production. Note the funding deadline is 5 minutes, so an order left sitting will already have expired.
assetAmountmust match the order’s funding asset (code and chain), else400 VALIDATION_ERROR. An order that named its ownsourcehas no address to fund and returns409 SANDBOX_ORDER_NOT_DEPOSIT_FUNDED.
.../wallets/{walletId}/deposits/simulate returns 404 WALLET_NOT_FOUND for it, exactly as the live wallet endpoints do.
Resolving a held funding transfer
When a transfer into a funding address is held for compliance review, resolve it on the order:POST https://api.sandbox.conduit.financial/v2/sandbox/orders/{orderId}/deposits/simulate/compliance-decision
approve releases the hold and the funds go on to fund the order; it is available only while the review is still open, and returns 409 once the review has already rejected the transfer — a rejected review can only be rejected. reject holds the funds permanently: they neither fund the order nor go back, the order goes unfunded and expires, and the transfer’s own transaction reads failed with no failureCode. 202 Accepted returns the order as of the call; poll GET /v2/orders/{orderId} for the outcome. 404 SANDBOX_ORDER_NO_PARKED_FUNDING means nothing is currently held for review.
For a full walkthrough — register the sender, create the order, read the address, fund it, watch it auto-execute — see Offramps.
Crypto deposit — sender-information gate
The sender-information gate fires when the receiving VASP needs originator details to satisfy Travel Rule. In sandbox there are two ways to drive it deterministically — pick whichever is easier for the test you’re writing: Driver 1 — source-address suffix. Pass asourceAddress ending in DE5E11F1. The gate fires regardless of the amount or whether the address is pre-registered.
"10000" USDC, which is 10_000.000000 at full precision). The gate fires regardless of address or suffix, matching the FATF R.16 / FinCEN funds-transmittal posture where high-value transfers always require originator information.
Below-threshold waiver. When a Travel Rule dollar threshold is configured
for your environment, a crypto deposit in a US-dollar stablecoin (USDC, USDT,
PYUSD) whose value is strictly below that threshold skips the
sender-information gate entirely: no
transaction.awaiting_sender_information, no 30-day deadline. The deposit is
still compliance-screened and releases only on a clean screen — so it
shows a compliance screening on the transaction, then completes. Precedence:
the DE5E11F1 suffix (Driver 1) and the 10,000-unit amount driver (Driver 2)
both still force the gate, so a deposit that trips either one requires sender
information regardless of the waiver. The waiver applies only when neither
driver fires.status: pending and your webhook endpoint receives transaction.awaiting_sender_information with a daysRemaining count and a deadlineAt timestamp.
Re-emission cadence.
transaction.awaiting_sender_information fires once
on initial park, then re-emits as the deadline approaches with the updated
daysRemaining. Terminal failure emits daysRemaining: null and persists
failureCode: sender_info_timeout on the transaction row.daysRemaining and deadlineAt fields in the webhook payload always reflect the real 30-day deadline, even in sandbox. deadlineAt is detectedAt + 30 days and daysRemaining is approximately 30 on the initial fire. Your webhook handler should branch on these values as if they are live-equivalent — they are.
What sandbox compresses is the server-side timeout: instead of waiting 30 days, the timeout fires after ~10 minutes. So the deposit auto-fails fast in a single test run, but the contract fields your code sees stay identical to production. This is the central sandbox promise: live-equivalent payload contract, compressed timeout.
Option A — wait for auto-timeout
Do nothing. After ~10 minutes the sandbox timer fires and the deposit auto-fails withfailureCode: sender_info_timeout; your webhook endpoint receives transaction.failed.
Polled GET after timeout. The public GET /v2/transactions/:id response surfaces failureCode: sender_info_timeout, matching the transaction.failed webhook payload. The polled response and the webhook never drift.
Option B — resolve manually via simulate endpoint
Call the simulate endpoint to provide sender information and clear the gate immediately. Returns200 OK with the deposit at its current state.
POST https://api.sandbox.conduit.financial/v2/sandbox/customers/{customerId}/deposits/{depositId}/simulate/sender-info
200 OK returning the deposit at its current state. The gate clears, the auto-timeout timer is cancelled, and the deposit proceeds to its next phase.
Crypto deposit — originator-identity screening
A crypto deposit whose sender is not yet known is compliance-screened twice: once on the source address the moment it lands (before you provide sender information), and again on the originator identity once you supply it. A deposit can therefore clear the source-address screen at arrival — and park on the sender-information gate — yet still be held when the originator identity is screened. While parked on the gate, the deposit already carries a compliance screening for the arrival check; the identity check is a second, independent screening. To exercise the identity screen in sandbox, provide a reserved originator name when you resolve the gate. The name is matched case- and whitespace-insensitively; use alegalName (business) or a firstName/lastName that composes to the reserved value (individual).
Reserved originator names (matched on the originator identity)
† A held deposit parks for a compliance decision rather than failing outright; resolve it with
POST https://api.sandbox.conduit.financial/v2/sandbox/transactions/{depositId}/simulate/compliance-decision ({ "outcome": "reject" } freezes with compliance_hold). Once compliance has rejected the deposit, { "outcome": "approve" } is not available — a rejected case can only be terminalized via reject.
A non-reserved originator name does not override the source-address (crypto) or account-number (fiat) outcome: the identity screen clears, but if the deposit’s address or account number still carries a compliance suffix (for example a rejected or sanctioned scenario), that outcome still applies. A non-reserved name on an otherwise-clean deposit takes the happy path and proceeds.
The reserved originator names match on any deposit’s originator identity,
not just crypto. A fiat deposit whose
senderInfo.name equals a reserved
value is held on the same identity screen. Use a distinct sender name on fiat
happy-path tests to avoid tripping it.Force a returned deposit
Both the fiat and crypto deposit simulate endpoints accept an explicitoutcome field that pre-decides the compliance branch at ingestion time. The values are:
"completed"(default) - deposit clears compliance and credits the destination balance."frozen"- deposit terminates asfailedwithfailureCode: "compliance_hold". Equivalent to passing a suffix that resolves to a non-CLEAR compliance decision, but explicit."returned"- deposit terminates asfailedwithfailureCode: "returned_by_sender". Models a fiat-rail return or a crypto reversal from the originating institution before credit. There is no suffix that triggers this branch.
202 Accepted returns the externalReference to poll on. The deposit then terminates failed and your webhook endpoint receives transaction.failed with failureCode: "returned_by_sender". The same outcome field is accepted on the crypto deposit simulate endpoint with identical semantics.
When outcome is set, it takes precedence over the suffix-driven branch. Pass outcome: "completed" (or omit the field) to keep the suffix protocol in effect.
Force a parked deposit terminal
After a deposit transaction exists,POST https://api.sandbox.conduit.financial/v2/sandbox/transactions/{depositId}/simulate/terminal can force the deposit to a terminal outcome. Use this when you need to pre-empt a compliance park, sender-information wait, or slow async path after locating the deposit id through GET /v2/transactions?type=deposit. Body is { "outcome": "completed" | "failed", "utr"?: "...", "reason"?: "..." }. outcome: "completed" posts the incoming deposit first when needed and then approves it; outcome: "failed" compensates an already-posted incoming deposit before writing the failed terminal state. Returns the deposit at its current state.
Suffix catalog
Suffixes are matched against the last 8 characters of the source identifier:- Crypto deposits: last 8 hex characters of
sourceAddress(case-insensitive on EVM; Base58 verbatim on Tron and Solana). - Fiat deposits: last 8 digits of
senderInfo.accountNumber(non-digit characters stripped before matching).
Crypto deposit suffixes (matched on sourceAddress)
Fiat deposit suffixes (matched on senderInfo.accountNumber digits)
† On deposits, every non-CLEAR compliance classification (high-risk or sanctions match) holds the deposit for a compliance decision rather than failing it outright — it stays
pending until the decision lands, and it is never credited in the meantime. Resolve it in sandbox with POST https://api.sandbox.conduit.financial/v2/sandbox/transactions/{depositId}/simulate/compliance-decision and { "outcome": "reject" }, which freezes it; approve is not available on an already-rejected review. The public failure code is compliance_hold in all cases; the underlying classification is recorded on the dashboard for audit. If the fiat deposit is the source funding event for the oldest pending autoExecute: true ONRAMP order that matches the deposit tuple and its amount could cover that order’s total debit, that order also emits order.failed with reasonCode: "provider_rejected".
The
DEAA8157 suffix (crypto) and 95009003 suffix (fiat) trigger an
elevated-risk compliance classification that is recorded internally for audit.
At current thresholds this classification routes APPROVED on the public
surface, so there is no observable difference from a clean deposit. Use these
suffixes to exercise audit-trail emission only; do not branch your integration
logic on them.Webhook events
transaction.failed payloads carry a failureCode when the cause is actionable. transaction.awaiting_sender_information includes daysRemaining and deadlineAt — these always reflect the real 30-day deadline (the same values your production handler would see). Only the sandbox internal timer is compressed to ~10 minutes so the timeout path is fast to test.
Errors
See Errors for the full catalog. Deposit-relevant failure codes:COMPLIANCE_HOLD— compliance screening returned a non-CLEAR decision (high-risk or sanctions match); the deposit is frozen and cannot be credited.RETURNED_BY_SENDER— the inbound transfer was returned by the originating institution before it could be credited. Sandbox triggers this branch via theoutcome: "returned"field on the deposit simulate endpoint.SENDER_INFO_TIMEOUT— the sender-information gate expired before details were provided; resubmit with originator details included up front.
Diagrams
Crypto deposit state machine
Sender-information gate — sandbox auto-pilot timeline
Related pages
- Sandbox overview — full sandbox posture and what’s mocked
- Withdrawal failure paths — compliance magic-suffix catalog for withdrawals and deposits
- Withdrawals — crypto withdrawal lifecycle and cosign flows
- Virtual Accounts — virtual account model and deposit instructions