On a blockchain, a mistake is final. If your code sends a payout to the wrong address, or sends it twice, there is no bank to call and no "reverse" button. That is why the first real payment your app makes should not be its first payment at all.
A sandbox is a copy of the platform that runs on fake money. You get a separate key, a separate database and the same API. You can break things there as often as you like, and nobody loses a cent.
This guide shows how to use a sandbox well: what to test, in what order, and the mistakes that still slip through when people test only the happy path.
#What a good sandbox gives you
Not every test mode is equal. Some only return canned answers, so your code passes in testing and then meets a new error on day one of real traffic. A useful sandbox should:
- run the same code paths as production, not a mock;
- let you create money on demand, because you cannot send real coins to it;
- answer with the same errors production would;
- move your test payouts through the same statuses as real ones;
- keep test money fully separate from real balances.
Our sandbox is built this way. A simulated deposit is a real deposit record pushed through the real crediting logic, with your fees and the deposit minimum applied. A sandbox payout walks the real state machine, from queued to confirmed, just without a chain underneath. The test data lives in its own database, and the live ledger never sees it.
#Step 1: get a sandbox key
You can get a sandbox key with one request, no signup needed:
curl -sX POST https://merchant.cryptoinfra.build/sandbox/v1/signup-keyThe response gives you an api_key that starts with ck_sandbox_, and the base_url to use. Put both in environment variables, never in your code:
export CRYPTOINFRA_KEY=ck_sandbox_your_key
export API=https://merchant.cryptoinfra.build/sandboxGoing live later means changing these two values and nothing else. If your code needs a code change to go live, something is hard-coded that should not be.
#Step 2: test your webhook handler on its own
Today the sandbox does not deliver webhooks. That is fine, because a webhook handler is easy to test without us, and it is better tested in isolation anyway.
Pick a test secret. Write a small script that takes a sample event body, signs the exact bytes with HMAC-SHA256 and that secret, and posts it to your handler with the x-cryptoinfra-event and x-cryptoinfra-signature headers. Now you can send a good event, a repeated event and a forged one whenever you like. Our guide on receiving crypto webhooks safely shows the event bodies and the signature check.
While you test against the sandbox, let your app learn results by reading the API, as the next steps do. That is the habit you want in production too.
#Step 3: create a user and fund them
Create a user with the id they have in your own system. They get a permanent TRON address.
You cannot send real USDT to a sandbox address, so the sandbox has a faucet instead:
curl -sX POST $API/v1/simulate/deposit \
-H "Authorization: Bearer $CRYPTOINFRA_KEY" \
-H "Content-Type: application/json" \
-d '{"external_user_id":"<your user id>","asset_id":"usdt_trc20","amount":"<amount>"}'This credits the user through the same logic a real transfer goes through. Read GET /v1/users/{id}/balances to see it land. Try it with USDT, USDC and TRX, because each asset has its own decimals and its own minimum.
#Step 4: send a payout and watch it finish
Ask for a fee quote, then send a withdrawal with an idempotency_key. You get 202 and the status queued. A few seconds later the sandbox moves it through signing and broadcast to confirmed.
Read the withdrawal with GET /v1/withdrawals/{id} until it is final. Get into the habit now: in production a webhook will tell you sooner, but the read is always the answer. See the payouts page for how statuses work end to end.
#The test plan: more than the happy path
Most bugs live in the cases that "never happen". The sandbox is where you make them happen on purpose. Work through this list before you go live:
| Test | What you should see |
|---|---|
| Simulate a normal deposit | Status credited, balance goes up |
| Simulate a deposit below the minimum | Status ignored_dust, no credit, no surprise |
| Post the same signed event to your handler twice | It credits only once |
| Post an event with a wrong signature | Your handler rejects it |
| Send a payout, then repeat it with the same key | Only one payout exists |
| Repeat the key with a different amount | 409 idempotency_conflict |
| Send a payout larger than the balance | 409 insufficient_balance, nothing queued |
| Send to an invalid address | 400 invalid_address, nothing queued |
| Kill your payout worker mid-request, restart it | It reuses the stored key, one payout |
The dust case matters more than it looks. A deposit below the asset's minimum is not credited, in the sandbox and in production alike. If your app shows "payment received" from your own records before the platform credits it, this is where you will find out.
#What the sandbox does not cover
Be honest about the gaps, so they do not surprise you later:
- Cards. The sandbox has no card rail. Card calls answer
503 cards_disabled. Use it to check that your code handles that error cleanly, then test card flows carefully with small real loads. Our cards page explains how loads work. - The real network. Sandbox payouts confirm without TRON, so real network fees and real confirmation times are not part of the test. Always read the fee from the quote, never from a number you saw in testing.
- Webhook delivery. Events are not sent from the sandbox, so the first real delivery happens on live. Register your live webhook URL and check that the first event reaches it and passes the signature check.
- Your own hosting. A sandbox cannot tell you if your production server, firewall or DNS is set up right.
#Going live: a short checklist
- [ ] Swap the key and base URL in your environment, not in your code.
- [ ] Register your production webhook endpoint and store its new secret.
- [ ] Confirm your handler verifies signatures with the live secret.
- [ ] Turn on two-factor sign-in for everyone on your dashboard team.
- [ ] Make one small real deposit and one small real payout, and read both back.
- [ ] Set your own fee, if you charge one, and check it on a real quote. Live numbers are on our pricing page.
#Common mistakes
| Mistake | What goes wrong | Fix |
|---|---|---|
| Testing only the happy path | The first real error breaks production | Run the full test plan above |
| Hard-coding the sandbox URL | Live traffic goes to the sandbox | Read it from the environment |
| Skipping signature checks "for now" | They never get added | Verify signatures from day one |
| Keeping your test signing secret | Live events fail to verify | Use the secret your live endpoint gets |
| Copying a fee from a test run | Users see a wrong number | Always show the live quote |
#In summary
A sandbox is cheap insurance. Get a key, fund a user with the faucet, push payouts through every status, and test your webhook handler with events you sign yourself. Then break things on purpose: repeat events, reuse keys, send too little and too much. When all of that behaves, going live is a change of two settings.
The quickstart in our docs has every call in order, and the developer overview shows the rest of the API.
Ready to build crypto payments under your own brand? Sign up free and start testing in the sandbox today.