Skip to main content

Sandbox behaviour

The digital-asset sandbox — https://api.sandbox.cashxchain.com and app.sandbox.cashxchain.com — talks to our crypto-asset service provider's own sandbox. Nothing real moves: no real or testnet asset, and no bank transfer, is ever credited to a sandbox wallet or IBAN. Read this page before you test, so that what the provider's sandbox does not do is not mistaken for a fault.


How money gets in​

Way inIn the sandbox
Test funds — POST /v1/sandbox/banking/accounts/{account_id}/simulate-deposit, or Add test funds in the web appThe way to fund a digital-asset account. Booked within seconds: balance, a transaction, payin.received
An on-chain transfer to one of your addressesNever arrives. Do not send real or testnet assets to a sandbox address
EUR by SEPA to the USDC account's IBANNever arrives, and cannot be simulated: the provider's sandbox refuses it
A convert from your sandbox EUR account, where offeredThe estimated USDC is deposited into your Solana wallet and booked within seconds
A send by @handle from another sandbox organisationArrives — see below

The faucet (POST /v1/sandbox/banking/faucet) credits bank accounts only: a digital asset is 422 asset_unsupported. It takes a session or an API key with payments:write, so a key-only integration can fund the EUR account a convert starts from — see Test money.

Credits held as a possible refund​

Test funds are always credited, whatever you sent before: they are recorded before the provider is asked, and the deposit they become is booked as yours.

Money back from one of our own payouts is never booked as a pay-in. Any other credit that names no transfer of ours — not test funds, not EUR through the USDC account's own IBAN, not a convert — is therefore held as a possible refund when it is 90 % to 105 % of what a send from the same wallet took in the last 14 days:

That sendCounts
To a bank account — an IBAN, us_bank, gb_bank, clabe, pix, a send-backSettled or not: a bank can return a payment after it settled
On chain, by @handle, a swapUntil it settles: one that fails after taking the money, or comes back, still counts

Held, the credit shows no transaction and no payin.received, and it needs an operator to release it — even though the wallet's live balance from the provider may already show it.


Which sends finish​

SendIn the sandbox
On chain, same chainsettled with a tx_hash once the provider's sandbox completes it — when
Across chainssettled with a tx_hash, as on the same chain
By @handlesettled as on chain, with no tx_hash (by design)
To an IBANSettled by the sandbox at the earliest a minute after the provider accepts it, and only once the money has been seen leaving the wallet — below
gb_bank (Faster Payments), clabe (SPEI), pixSettled by the sandbox, as to an IBAN
us_bank (ACH, wire)Left to the provider's sandbox for five minutes. It was seen to complete an ACH send itself, within 168 seconds of accepting it (2026-11-03); a send it completes reads settled with no simulated. One still in flight after five minutes — a wire, not yet observed, or an ACH send it has not completed — is settled by the sandbox, as to an IBAN. simulate-completion settles one at once, whatever its wait
SwapSettled by the sandbox, as to an IBAN, once the provider's sandbox has been asked to deposit the bought asset into the wallet. The deposit shows as the swap's arrival once the wallet is read, within seconds
Send back to your EUR accountSettled by the sandbox, as to an IBAN. Its arrival is simulated too: the EUR is booked on your sandbox EUR account within about two minutes, or at once with simulate-arrival
Convert from your sandbox EUR account, where offeredsettled in the 202; the USDC arrives within seconds

Poll GET /v1/banking/payouts/{id} for a send's status; there is no webhook for it.

When a send reads settled​

The public sandbox receives no webhooks from the provider, so what moves a send is the sandbox's own reads:

  • Its status, from the provider: every 15 seconds for the first ten minutes after the provider accepted it — up to 10 payouts a read across the whole sandbox, the least recently read first, so with more in flight each is read every few reads — and about every two minutes while it is in flight. While the provider throttles the 15-second read or does not answer, that read pauses, for up to two minutes; the two-minute read goes on.
  • Its wallet's history, where the provider's debit shows: whenever a status read finds the provider has moved the send; for a send the sandbox settles, 15 seconds before its wait is over and, while the debit is not seen, at the wait and 30 seconds, 1½, 3½ and 7½ minutes after it; and at least every five minutes. Each read comes within about five seconds of being asked for, or once the round of reads under way has finished.
SendReads settled
On chain, across chains, by @handleAt the first status read after the provider's sandbox completes it. In our runs it completed them from about 25 seconds to a little over a minute after accepting them, and in one run between about 45 seconds and 3½ minutes after
To an IBAN, gb_bank, clabe, pix, a send-back, a swapWhen the sandbox settles it: with the default one-minute wait, 60 to 75 seconds after acceptance where the provider has listed its debit by about 45 seconds; otherwise within about 15 seconds of the first read of the wallet after the debit is listed. When the provider takes the money, it does not say
us_bankAt the first status read after the provider's sandbox completes it, where it does within five minutes. Otherwise when the sandbox settles it: by default 300 to 315 seconds after acceptance where its debit is listed by about 285 seconds
Any of the sends the sandbox settles, with simulate-completionAt once, whatever its wait, once its debit has been seen

These are the sandbox's own reads; when the provider's sandbox completes a send, or lists its debit, it does not say in advance.

Sandbox addresses and tx_hash values are not on any public chain: neither can be looked up on an explorer.

Sends the sandbox settles​

The provider's sandbox accepts a send to an IBAN or to a bank account outside SEPA, a send-back and a swap, and then leaves it in flight — for good, except that it was seen to complete a send over ACH itself (us_bank, above). In production these settle. So that every send reaches settled in the sandbox too, the sandbox settles them itself:

  • Only once the money has left the wallet: once the wallet's history shows the debit naming the send's transfer, read as above. Until then the send stays processing.
  • From a minute after the provider accepted it — five minutes for a us_bank send, so that the provider's own completion of an ACH send comes first — once that debit has been seen: at the first run of the sandbox's settling pass after both, which runs every 15 seconds. simulate-completion skips the wait but not the debit: until the debit is seen it answers 409 debit_not_seen.
  • Only where the provider left it. A send the provider has moved on — completed, on its way back, refunded, or in a state support resolves — follows the provider, as everywhere. The sandbox settles a send only while it is still submitted or processing: one support is resolving (needs_reconciliation) is left to support, and one the provider completes meanwhile reads settled with no simulated.
  • A swap's bought asset is deposited by the provider's sandbox first: the amount the provider's receipt states, else the estimate a quote of the swap shows (destination_amount_estimate), cut to the cent. It is booked into your account in the asset bought and shown as the swap's arrival (matched_by: "transfer") once the wallet is read, within seconds of the settlement: the 200 may still show arrival: null, so read the payout again. GET /v1/transactions lists it as internal with no counterparty — not as a deposit on chain, though the provider's sandbox records it like one. It is deposited once, however often the completion is tried.
  • Settled as any payout: status settled with settled_at, and simulated: true on the payout. The field is absent on every payout the sandbox did not settle itself, and always in production. Its hold is released as any finished payout's is: for a send whose balance is read a minute or more after the settlement.
  • What left, on a send-back. In production a send-back shows the provider's receipt of it: source_amount, source_currency and exchange_rate. The provider's sandbox never sends one, so a send-back settled here shows what the sandbox saw in their place: source_amount and source_currency, the USDC the wallet has been seen to give for its transfer (every debit naming the transfer, summed: "5.640000" USDC for 5.00 EUR), and exchange_rate, the EUR that arrived per USDC that left — amount ÷ source_amount to six places ("0.886525"), as the provider defines its receipt's rate. The figure is worked out each time the payout is read: the send-back settles on the first debit, and an entry for its gas read later counts too. No fee is stated, and what the wallet gave need not be the figure a receipt states in production. A send-back whose conversion the provider did report shows the provider's figures, also where that report is recorded after the settlement; one with a debit naming its transfer whose amount could not be read shows none of the three.
  • Not on the other sends settled here. A send to an IBAN or to a bank account outside SEPA fixes what leaves — its amount, in the asset — and only the provider's receipt states what the bank was paid: in the sandbox it carries no source_amount or exchange_rate. Nor does a swap: what left is its amount, what it bought its arrival, and the provider states no rate for a swap.

Never touched: a send on chain, across chains or by @handle (the provider's sandbox completes those itself — when), and the convert (settled at once).

Settling a send now​

POST /v1/sandbox/banking/payouts/{payout_id}/simulate-completion settles one of the sends above at once, whatever its wait — a minute, or five for a us_bank send; its debit must still have been seen. A session, or a key with payments:write. No body and no Idempotency-Key: the payout is the key.

Bash
curl -s -X POST "$CXC_BASE/v1/sandbox/banking/payouts/$PAYOUT_ID/simulate-completion" \
-H "Authorization: Bearer $CXC_KEY" | jq '{status, settled_at, simulated, arrival}'

200 with the payout, as GET /v1/banking/payouts/{id} shows it: settled by this request or an earlier one (simulated: true), or finished before — settled by the provider, failed, cancelled or returned — and left as it is. Asking again does nothing twice.

StatuserrorWhen
404not_foundNot one of your payouts
409not_simulatabledetails.reason: debit_not_seen — the money has not yet been seen leaving the wallet; the wallet is read again at once, so ask again in a few seconds. not_accepted — the provider has not accepted it. needs_reconciliation — support is resolving it. provider_state — the provider reports it in a state its sandbox moves on from (details.partner_status): it follows the provider. A us_bank send the provider's sandbox has completed answers this until the next read of its status records the completion; then 200, settled with no simulated. wallet_not_open, asset_not_on_network — a swap whose bought asset cannot arrive: no wallet carrying it is open on the chain, or it is no longer offered there. arrival_not_seen — a swap whose bought asset was asked for over 23 hours ago and never seen: it stays processing for support
422not_eligibledetails.reason: not_a_digital_asset_send; completes_at_provider — on chain, across chains or by @handle; no_casp_layer, no_digital_rail, provider_not_sandbox — this deployment settles nothing itself
429rate_limitedMore than 30 requests an hour from your account; or the provider's sandbox throttled the read (the message says upstream partner rate-limited the request): ask again shortly
503service_unavailableBeing completed by another request at that moment, the provider could not be read (a provider timeout can also be 504 gateway_timeout), or its sandbox did not take a swap's bought asset. Ask again shortly: it is settled, and what a swap bought deposited, once

Holds of sends in flight​

While a send is in flight, and for about a minute after it finishes, its wallet holds what it may still take:

  • Until the wallet's history shows the debit naming its transfer: the whole send — its amount and its chain's headroom (1 %; 5 % on Ethereum), a send-back its whole max_source_amount.
  • From a minute after that debit is read: only the headroom above it, until the send settles, fails or comes back.
  • For about a minute after it finishes: what is left of its hold. Each send reads the wallet's balance afresh, and that read may not show the finished send's last debit yet. A send whose balance is read less than a minute after another finished still counts what is left of that one's hold — its headroom, or all of it where its debit was first seen less than a minute before the read. A send read a minute or more after counts none of it.

So from the provider's debit until a minute after the wallet read that sees it, the balance and the hold count the same money twice, and what is left of a hold counts for about a minute after its send finishes: available in the refusal is that much lower meanwhile, and a send refused then fits once the minute is over, under a new Idempotency-Key. The wallet is read within seconds of a status read that finds the provider has moved a send — every 15 seconds for a send's first ten minutes, above — and at least every five minutes.

A send refused when its transfer was created (below) holds nothing. A later send can be refused 400 insufficient_balance sooner than the balance suggests. The message names what is free and what the send needs, the amount and the headroom apart:

Plain text
Insufficient balance: available 9.10 USDC, needed 10.10 USDC: 10.00 USDC for the payout plus 0.10 USDC headroom, held while it is in flight

On a send-back, the amount is the USDC estimate of the EUR it sends, and the headroom the rest of its max_source_amount. When testing several sends in a row from one wallet, add test funds to spare.


A refusal when the transfer is created​

Some refusals come from the provider only when the transfer is created — a Stellar account with no trust line for the asset is one. Refused in answer to the only request sent, such a send answers 422 destination_not_payable with details.reason: provider_refused, recorded: details.payout_id, details.idempotency_key_spent: true. The payout is failed at once and holds nothing, and your next send is not held behind it.

  • A refusal of what you sent carries the provider's sentence in the message, and names the field in details.provider_field when the provider named one, as it names it (destination_stellar_account). Change it and send again under a new Idempotency-Key.
  • Any other refusal — a permission or compliance refusal, an object it does not know, a rail it will not use — is in our words: the provider refused this payout for this account; contact support before sending it again under a new key. A rail refusal still names details.provider_field: "destination.payment_rail": tell the two kinds apart by the message, not by that field.

The provider refusing our own credentials at that point is 503 service_unavailable, recorded the same way: ours to fix, the payout failed, the next send not held behind it; send again later under a new key. Anything else at the create — no answer, an outage, a rate limit, a conflict, or a refusal after a retried attempt — is still 503 payout_outcome_unknown. Send it again only under the same key. Until it reads submitted or failed (seven minutes or more), your other sends answer 409 earlier_payout_unconfirmed. See Errors.

To avoid a refusal of an on-chain send, quote first. Where the deployment runs the provider's dry run, the quote answers valid: false with the provider's reason — see Quotes. Fix what it names before sending. A quote to a bank account, or of a swap, is an estimate only and does not foresee a refusal.


An @handle arrival​

A send by @handle is booked once, on the recipient's account, as an internal payment from the sender: type and rail internal, and the sender's name as their handle shows it (counterparty.name, payer_name) — never the sender's wallet. The provider's sandbox records the money reaching the recipient's wallet as a plain deposit that does not name the send, so it is matched to the send by the wallet, the asset and the exact amount — each send once.

Test funds of exactly that amount, simulated into the same wallet while the send is on its way, may be shown as the send, and the send's own arrival as the test funds. Amounts are right and nothing is booked twice; to keep the two apart, give test funds an amount unlike any send by @handle on its way to you.


Regions​

The provider decides routes by your business's country, the one your account was opened with:

A business in the EEAA business outside the EEA
USD to a US account (us_bank)YesYes
GBP, MXN, BRL (gb_bank, clabe, pix)No — 422 corridor_unsupported, and approving such a trusted beneficiary is refused tooYes
USDT across chains, and by @handleNoYes

digital_actions and GET /v1/banking/capabilities show what your account is offered. rails in GET /v1/banking/kyb/digital-assets shows which payout rails the provider has enabled for you: a bank send is offered only over an enabled rail. The sandbox approval requests and enables all of them. An account approved before GBP, MXN and BRL were added holds USD and EUR (SEPA) only: call the approval again to add them, and check rails.


Webhooks​

EventIn the sandbox
payin.receivedSent for each credit booked on your accounts: test funds, a convert's USDC, a send-back's simulated EUR arrival, a held credit booked once you open its asset, a send by @handle, a swap's bought asset. data: payin_id, amount, currency (the asset), rail (the chain, such as solana; sepa for a bank credit; internal for a send by @handle and a swap's bought asset), payer_name (null on chain and for a swap's bought asset; the sender's name for a send by @handle), reference, booked_at. It does not name the account: an asset has one account, so currency identifies it
Payout eventsNone. No event for any send's status — poll GET /v1/banking/payouts/{id}

No payment mails are sent in the sandbox. See Webhooks.


Limits​

  • Sandbox tools — simulate-deposit, simulate-arrival, simulate-completion, the sandbox approval and the faucet each take at most 30 requests an hour from your account, a refused request included (429 rate_limited, the message says how many minutes are left). The sandbox convert has a window of its own.
  • The approval and test funds call the provider's sandbox on a key every tester shares: a script looping on them is throttled there too.
  • Send caps, per asset: 10,000 a send, 50,000 a day, 250,000 a month.
  • Rates in the provider's sandbox can be months old: show rate_as_of from a quote.

What the sandbox does not do for digital-asset accounts​

  • Statements — not available for digital-asset accounts (400 bad_request, naming the account's asset). Use GET /v1/transactions for credits and GET /v1/banking/payouts for sends.
  • Payout webhooks — none; poll.
  • Production — none of this is offered in production yet: there, digital-asset accounts, wallets and sends answer as Capabilities describes.