Sandbox mode
The same API, completely separate data, and outcomes you choose.
Sandbox is not a separate product or a mock server. It is the same API, the same routes and the same validation, with data kept completely apart from live. What changes is where the money comes from: no operator is contacted, and the outcome is decided by the test number, card or account you use.
How the environment is chosen
By the key. There is no parameter, no header and no flag.
| Prefix | Environment |
|---|---|
pk_test., sk_test., rk_test. | Sandbox |
pk., sk., rk. | Live |
A sandbox key cannot read live data, and the reverse is equally true: sandbox and live are completely separate. That is also why a payment you created in sandbox will never appear in your live Konsole, and why there is nothing to filter.
Deleting sandbox data does not exist as a mistake you can make
There is no button and no endpoint that wipes an environment. But there is also no migration path: nothing you build up in sandbox, customers, beneficiaries, endpoints, comes with you when you switch to live. Recreate them deliberately.
What is real
| Real in sandbox | Meaning |
|---|---|
| Validation | The same rules, the same 422 messages |
| Limits | Amount floors and ceilings are enforced identically |
| Events and webhooks | Real events, real signatures, real retries |
| Idempotency | The same behaviour on a repeated key |
| Rate limits | The same ceilings, counted apart from live: a sandbox load test does not spend your live minute |
| Balance | A separate sandbox balance, credited by sandbox payments |
| Platform fees | 2% is charged on successful payments and transfers, producing fee.charged |
The last one surprises people: a sandbox payment of 5,000 XAF credits less than 5,000 to your sandbox balance, exactly as a live one would.
What is simulated
| Simulated | How |
|---|---|
| The operator | The test number decides the outcome, nothing is dialled |
| The card network | The test card decides the outcome |
| 3D Secure | The hosted checkout asks for a code sent by email, then the test card decides the outcome |
| The bank | The last two digits of the account decide the outcome |
| Crypto | A deposit address per outcome is generated; sending one of them as wallet_address decides the outcome |
| Timing | A fixed delay rather than a real network |
The delays are short and fixed, which is what makes an automated test suite possible.
| Operation | Delay before the terminal status |
|---|---|
| Payment | 3 seconds |
| Refund | 2 seconds |
| Transfer | 2 seconds |
Asynchronous, not instant
A sandbox payment is still pending when the call returns. A test that asserts succeeded
immediately after creating it will fail. Wait for the webhook, or poll until the status is
terminal.
Outcomes are deterministic
This is the part worth internalising: nothing in the sandbox is random, with one narrow exception.
| Object | What decides the outcome |
|---|---|
| Payment, mobile money | The suffix of the payer number |
| Payment, card | The exact card number |
| Payment, bank | The last two digits of the account |
| Payment, crypto | Which crypto.deposit.scenarios address you send as wallet_address (none: success) |
| Refund | The original payment's card or phone |
| Transfer | The beneficiary's test number or account |
The exception: a refund on a payment with no payment method attached falls back to a 70% chance of success. In practice this only happens on payments created without a payer, which is not a flow you ship.
Transfers are deterministic too
A sandbox transfer to a mobile money beneficiary requires a recognised test number and refuses
anything else with a 422 that lists the valid ones. There is no probability involved.
The sandbox balance
Payouts need money. Sandbox payments credit a sandbox balance, and transfers debit it, so the order of your test matters: create and complete a payment before testing a payout, or the transfer fails for insufficient funds exactly as it would in production.
A failed transfer returns the amount to the balance. The 2% platform fee is not returned, which mirrors live behaviour.
Crypto is off unless asked for
The crypto channel is disabled in sandbox by default, and it is switched on for the whole sandbox platform, not per account. When it is enabled, a deposit address and a payment URI are generated per scenario, valid for 30 minutes, and the address itself is deterministic, so you can reproduce the same scenario twice.
Nobody sends funds in sandbox, so the answer to POST /payments/{id} on a crypto channel also
carries a simulator_url. It is a signed link, valid for 30 minutes, to a page with four buttons,
SUCCESS, FAILURE, TIMEOUT and CANCELED. Pressing one queues that outcome, and the payment
then moves on as it would after a real deposit, webhooks included. The field never exists in live.
3D Secure asks for a code by email
A 3D Secure test card from the scenarios answers
202 with action: "confirm_3ds". On the hosted checkout, the payer is then asked for a six digit
code, which Wajub emails to the customer's address and to the account owner, so the challenge can
be completed even when the test customer has a placeholder email. The code is valid for 10 minutes
and allows 3 attempts. Once it is accepted, the card number decides whether the payment succeeds or
fails.