Skip to main content
The sandbox cluster is fully mocked. There is no real chain, no real banking connection, no third-party vendor calls. Every outcome is deterministic and driven by your request data — address suffixes for crypto deposits, account-number suffixes for fiat deposits, or an explicit 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 deposit transaction you can read back. Only the section below applies to them — see Deposit-funded orders for the concept.
Use fiat deposits when testing order-funded flows (onramps, conversions). Use crypto deposits when testing the inbound wallet flow including compliance and Travel Rule sender-info scenarios. Use the order-keyed endpoint when the order published a funding address instead of debiting a wallet you named.

Prerequisites

  • A sandbox API key for an active customer. Set SANDBOX_API_KEY, CUSTOMER_ID, VA_ID (virtual account), and WALLET_ID in your shell.
  • For fiat: the virtual account must be active for asset USD.
  • For crypto: the wallet must be active and 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.
A reference you choose is unique across the whole sandbox. The fiat dedup key is the provider plus the reference, with no organization in it, and every sandbox organization shares one mock bank. If another organization already used invoice-001, your call is treated as their replay: you get 202, but no deposit ever appears in your transaction list. Omit externalReference and let the sandbox derive one — the synthetic value hashes your organization in, so it cannot collide — or prefix yours with something unique to your integration. Crypto deposits are unaffected: their key includes the destination address, which differs per organization.

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 a senderInfo.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:
Synthetic txHash is deterministic. When you omit txHash, the sandbox derives one from a hash of (organizationId, customerId, walletId, chain, assetCode, amount, externalReference). Two identical request bodies produce the same txHash, and the second call re-acknowledges the existing deposit (idempotent on (chain, txHash)) rather than creating a fresh row. Pass an explicit random txHash per request to bust dedupe, for example TXHASH="0x$(openssl rand -hex 32)" then include "txHash": "$TXHASH" in the body.Dedupe also varies by finality state and sender. Two probes of the same tx at different finality states (e.g. submitted then finalized) ingest as separate rows. On the fiat side, two same-amount deposits with different senderInfo.accountNumber (e.g. swapping compliance suffixes) both ingest cleanly — no need to vary the amount.
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 in depositInstructions 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
This endpoint does not behave like the two above, and most of this page does not apply to it. The response is 200 OK with the order, not a deposit. The transfer itself still lands as an ordinary deposit transaction — readable on GET /v2/transactions, firing the standard transaction.* events — the same as any other deposit; see Deposit-Funded Orders for what it looks like.
Register the sending address first. A funding address accepts money only from an address the customer has registered:
Screening clears immediately in sandbox (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:
  • sourceAddress decides 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 readable deposit transaction (it reads failed, since nothing was ever credited) and the return is a readable deposit_return naming it. sourceAddress: null is refused with 400 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 with POST 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 originator is rejected. Registration replaces it: whether the sending address is registered is the only gate, and sender identity does not open it. Passing originator returns 400 VALIDATION_ERROR rather 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, and simulate/sender-info are all wallet-deposit features with no effect on this route.
  • Send the order’s totalDebit, not sourceAsset.amount — the order executes only once the funds cover the full debit including fees. Send less and it stays pending; 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.
  • assetAmount must match the order’s funding asset (code and chain), else 400 VALIDATION_ERROR. An order that named its own source has no address to fund and returns 409 SANDBOX_ORDER_NOT_DEPOSIT_FUNDED.
The wallet endpoint above cannot be used for one of these addresses: a Conduit-managed funding address is not a wallet you can name, so .../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 a sourceAddress ending in DE5E11F1. The gate fires regardless of the amount or whether the address is pre-registered.
Driver 2 — Travel Rule amount threshold. Send a crypto deposit at or above 10,000 in canonical asset units (e.g. "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.
The deposit parks at 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.
Sandbox vs. production deadline — what changes and what doesn’t: The 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 with failureCode: 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. Returns 200 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 a legalName (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 explicit outcome 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 as failed with failureCode: "compliance_hold". Equivalent to passing a suffix that resolves to a non-CLEAR compliance decision, but explicit.
  • "returned" - deposit terminates as failed with failureCode: "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.
Address format. EVM addresses must be all-lowercase OR a correctly EIP-55 checksummed mixed-case form. The mnemonic suffixes called out in this page are uppercase for readability; the wire-format addresses you send to the API are all-lowercase.

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).
Addresses and account numbers not matching any suffix take the happy path: compliance approved, deposit completes.

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 the outcome: "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