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 two deposit types:
  • 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.
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.

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:
FieldTypeRequiredDescription
assetAmountobjectyes{ "code": "USD", "amount": "1000.00" }. amount is a canonical decimal string at USD precision (2 decimals).
outcomeenumno"completed" (default), "frozen", or "returned". frozen parks the deposit in a sanctions-freeze terminal state. returned parks it in an AML-return terminal state. Equivalent to the senderInfo.accountNumber suffix catalog but explicit; takes precedence when both are set.
railstringno"ACH", "FEDWIRE", or "RTP". Defaults to ACH
senderInfoobjectnoSynthetic sender details (name, accountNumber, routingNumber, iban, bic, country). Pass senderInfo.accountNumber ending in a fiat AML suffix to force a compliance outcome (see Suffix catalog).
externalReferencestringnoCustom reference for the synthetic transfer
curl -X POST "https://api.sandbox.conduit.financial/v2/sandbox/customers/${CUSTOMER_ID}/virtual-accounts/${VA_ID}/deposits/simulate" \
  -H "x-api-key: ${SANDBOX_API_KEY}" \
  -H "idempotency-key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "assetAmount": { "code": "USD", "amount": "1000.00" }
  }'
201 Created with { "status": "received", "inboxEntryId": "..." }. The deposit enters the ingestion pipeline and the customer’s USD balance updates within a few seconds. 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 a senderInfo.accountNumber ending in a documented suffix to force a specific AML 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:
FieldTypeRequiredDescription
assetAmountobjectyes{ "code": "USDC", "chain": "ETHEREUM", "amount": "100" }. amount is a canonical decimal string at the asset’s precision (USDC has 6 decimals).
outcomeenumno"completed" (default), "frozen", or "returned". frozen parks the deposit in a sanctions-freeze terminal state. returned parks it in an AML-return terminal state. Equivalent to the sourceAddress suffix catalog but explicit; takes precedence when both are set.
sourceAddressstringnoSender address. Omit to get a synthetic deterministic address
txHashstringnoSynthetic transaction hash. Omit to get a deterministic synthetic hash (see Note below). Pass an explicit random value to bust the dedupe key when re-firing the same body.
originatorobjectnoOriginator details (entityType, legalName, country). Defaults to a generic sandbox sender on the happy path. The default is automatically suppressed when the sender-information drivers fire (sourceAddress ending in DE5E11F0, or amount at/above the 10,000-unit Travel Rule threshold) so the gate parks instead of clearing. Pass an explicit originator object to override and clear the gate. Pass null to suppress the default originator on the happy path (no other driver firing) — note that this alone does not force the gate when the source address is already registered.
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 is dedupe-rejected with { "status": "duplicate" } instead of a fresh deposit 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 AML suffixes) both ingest cleanly — no need to vary the amount.
curl -X POST "https://api.sandbox.conduit.financial/v2/sandbox/customers/${CUSTOMER_ID}/wallets/${WALLET_ID}/deposits/simulate" \
  -H "x-api-key: ${SANDBOX_API_KEY}" \
  -H "idempotency-key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "assetAmount": { "code": "USDC", "chain": "ETHEREUM", "amount": "100" }
  }'
201 Created with { "status": "received", "inboxEntryId": "..." }. The deposit enters the ingestion pipeline: compliance screens the source address (default sandbox originator clears automatically), and the balance updates within a few seconds. 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.

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 DE5E11F0. The gate fires regardless of the amount or whether the address is pre-registered.
curl -X POST "https://api.sandbox.conduit.financial/v2/sandbox/customers/${CUSTOMER_ID}/wallets/${WALLET_ID}/deposits/simulate" \
  -H "x-api-key: ${SANDBOX_API_KEY}" \
  -H "idempotency-key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "assetAmount": { "code": "USDC", "chain": "ETHEREUM", "amount": "100" },
    "sourceAddress": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaade5e11f0"
  }'
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.
curl -X POST "https://api.sandbox.conduit.financial/v2/sandbox/customers/${CUSTOMER_ID}/wallets/${WALLET_ID}/deposits/simulate" \
  -H "x-api-key: ${SANDBOX_API_KEY}" \
  -H "idempotency-key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "assetAmount": { "code": "USDC", "chain": "ETHEREUM", "amount": "10000" }
  }'
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 ~30 seconds. 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 ~30 seconds 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
curl -X POST "https://api.sandbox.conduit.financial/v2/sandbox/customers/${CUSTOMER_ID}/deposits/${DEPOSIT_ID}/simulate/sender-info" \
  -H "x-api-key: ${SANDBOX_API_KEY}" \
  -H "idempotency-key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "originator": {
      "entityType": "BUSINESS",
      "legalName": "Acme Corp",
      "country": "USA"
    }
  }'
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.

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 AML 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.
curl -X POST "https://api.sandbox.conduit.financial/v2/sandbox/customers/${CUSTOMER_ID}/virtual-accounts/${VA_ID}/deposits/simulate" \
  -H "x-api-key: ${SANDBOX_API_KEY}" \
  -H "idempotency-key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "outcome": "returned",
    "assetAmount": { "code": "USD", "amount": "1000.00" }
  }'
201 Created returns the public deposit transaction view (the same shape as GET /v2/transactions/:id). 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 workflow 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)

SuffixScenariofailureCode
DEAA4900AML approved (explicit happy path)
DEAA8E50 / DEAA5A4DAML non-CLEAR (high-risk or sanctions) — deposit frozen †COMPLIANCE_HOLD
DEAA8157AML risky — no observable difference on the public surface; the deposit completes as APPROVED. The dashboard records the risky classification for audit.
DE5E11F0Sender-information gate required — deposit parks; sandbox timer fires after ~30 sSENDER_INFO_TIMEOUT (if not resolved)

Fiat deposit suffixes (matched on senderInfo.accountNumber digits)

SuffixScenariofailureCode
95000000AML approved (explicit happy path)
95009001 / 95009002AML non-CLEAR (high-risk or sanctions) — deposit frozen †COMPLIANCE_HOLD
95009003AML risky — routes approved at current threshold
† On deposits, every non-CLEAR AML classification (high-risk or sanctions match) routes to a frozen terminal — the deposit cannot be credited until compliance 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 AML 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

EventWhen it fires
transaction.createdImmediately after the simulate call is accepted
transaction.awaiting_sender_informationDeposit parks at the sender-information gate. Two drivers: sourceAddress ending in DE5E11F0 (suffix-keyed, fires regardless of pre-registration) or deposit amount at/above the 10,000-unit Travel Rule threshold (amount-keyed, fires regardless of suffix).
transaction.completedDeposit reaches terminal completed state
transaction.failedDeposit reaches terminal failed state (AML non-CLEAR, sender-info timeout); fiat source deposits can also fail a matching amount-covered pending auto-execute ONRAMP order
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 ~30 seconds 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