Sandbox Testing
Simulate verification decisions, treasury funding, and deposits, and rehearse every failure path before real money moves.
What the sandbox is
https://sandbox.rasto.co with an sk_test_ key is the full product with
everything behind the API simulated. Same endpoints, same objects, same
statuses, same webhooks, really delivered and really signed. Verification
decisions and bank deposits are driven by you, through simulation endpoints
under /api/sandbox/simulate/*.
On the live host every simulator returns 403 sandbox_only.
Sandbox and production, precisely
| Dimension | Sandbox | Production |
|---|---|---|
| Endpoints, objects, statuses, error envelopes | Identical | Identical |
| Webhooks | Real: delivered and signed like production | Real |
| Verification | You decide, through the simulator | Decided by the real review |
| Bank deposits | Simulated with simulate/fiat_deposit | Real ACH and Fedwire credits |
| Wallet addresses | Derived the same way, on testnets | Derived the same way, on mainnets |
| On-chain delivery | Simulated | Real transactions |
| Treasury inventory | You fund it with simulate/treasury_funding | Funded operationally |
| Exchange rate | Fixed and deterministic. USD to USDC is at par | Live market rate plus spread |
| Simulation endpoints | Available | 403 sandbox_only |
Everything your code touches behaves identically, so a green sandbox integration is production ready.
Verification decisions
You are the decision engine in sandbox. The simulator runs the same shared decision path a real decision takes: statuses, per-requirement updates, and webhooks all behave identically.
curl -s $BASE/api/sandbox/simulate/verification_decision \
-H "Api-Key: $KEY" -H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"customer_id": "cus_...", "decision": "approved"}'
decision | Simulates | What to test |
|---|---|---|
approved | A clean pass | The happy path, and endorsement.approved. Requested endorsements approve alongside |
request_for_information | A retryable issue on proof of address | Your requirement-bucket UI and the PATCH-then-submit loop |
rejected | A final adverse decision | Your terminal-state handling |
The customer must already have a verification round, so call
POST /api/customers/{id}/verifications/submit first. Otherwise the
simulator returns 400 verification_not_started.
Treasury funding
Deliveries come from prefunded inventory, and the sandbox treasury starts empty. Fund the corridor you are testing first.
curl -s $BASE/api/sandbox/simulate/treasury_funding \
-H "Api-Key: $KEY" -H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"asset": "usdc", "chain": "solana", "amount": "10000"}'
Forgetting this is the most common sandbox surprise: deposits park in
orchestrating, exactly as they would in live during an inventory gap,
until inventory exists.
Bank deposits
curl -s $BASE/api/sandbox/simulate/fiat_deposit \
-H "Api-Key: $KEY" -H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"virtual_account_id": "va_...", "amount": "100.00",
"sender_name": "ANA SILVA", "remittance_info": "invoice 42"}'
The deposit arrives on the account's own rail and runs the real pipeline:
deposit, conversion, payout, with every webhook. Assert on the conversion's
fee_lines and delivered_amount in your integration tests.
Magic amounts
Failure paths need no special endpoints. Specific amounts trigger them.
| Amount | Result |
|---|---|
666.00 | Deposit returned, with the deposit.returned webhook |
777.00 | Deposit on_hold, with the deposit.on_hold webhook |
| anything else | Normal orchestration |
For the two magic amounts the status callback fires and no orchestration runs, so the response carries no workflow id.
Wallet deposits
Credit a held wallet directly, as if a deposit landed on chain:
curl -s $BASE/api/sandbox/simulate/wallet_deposit \
-H "Api-Key: $KEY" -H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"wallet_id": "wal_...", "asset": "usdc", "amount": "250.00"}'
This writes the same wallet_transaction row a real delivery would, so
balances and history read back through the code path you will use live, and
wallet.deposit_received fires.
A complete test checklist
Onboarding
Terms accepted, customer created with documents, submitted. Handle
approved, request_for_information plus the PATCH-then-submit loop,
and rejected. Also assert the 422 verification_incomplete you get
from submitting too early.
Money
Treasury funded, deposit landed. Verify the delivered amount, the
fee_lines, and the tx_hash. Then run 666.00 and 777.00.
Wallets
Create a wallet, credit it with simulate/wallet_deposit, read the
balance back, and run one operation end to end including the verify
step. Let one operation expire on purpose.
Webhooks
Every event kind processed idempotently. Replay one from the portal to
prove your dedupe works, and shuffle two events to prove your ordering
uses sequence.
Failure hygiene
Retry a POST with the same Idempotency-Key and expect a replay, not a
duplicate. Reuse a key on a different body and expect the 422. Send a
request with no key and expect the 400.
When all five pass in sandbox, work through Going Live.