Sandbox & testing
Build and test your whole integration without spending money. Sandbox keys call the same endpoints with the real catalogue and your real prices, but purchases are paid from a test wallet and nothing is sent to networks or billers.
How it works
1. Create a sandbox key
In Dashboard → Developer, choose Sandbox when creating a key. Sandbox keys start with ds_test_ and work straight away, even before your API access is approved.
2. Use the same base URL
There is no separate sandbox host. The key decides the mode: ds_test_ keys run in the sandbox and ds_live_ keys run live.
3. Spend test money
Your sandbox wallet starts with ₦100,000 of test money. Reset it any time from the dashboard or with POST /sandbox/reset.
4. Choose the result with test numbers
The last four digits of the phone, meter, smartcard or account number decide what happens. Use them to test success, failure, pending and webhooks.
5. Go live
Once API access is approved, swap the ds_test_ key for a ds_live_ key. Nothing else in your code needs to change.
Test numbers
Any number that does not end in one of these suffixes completes successfully.
| Ends in | Example phone | Example meter / smartcard | Result |
|---|---|---|---|
| anything else | 08031234567 | 45012345678 | successful (HTTP 201) with a test token, PINs or delivery details |
| 0002 | 08030000002 | 45010000002 | failed (HTTP 422). The sandbox wallet is refunded. |
| 0003 | 08030000003 | 45010000003 | pending (HTTP 202), then successful about 20 seconds later, with a webhook |
| 0004 | 08030000004 | 45010000004 | pending (HTTP 202), then failed and refunded about 20 seconds later, with a webhook |
| 0009 | — | 45010000009 | Verify endpoints return 400 VERIFICATION_FAILED |
Example — test a pending purchase
This returns pending first. Poll GET /transactions/{reference} or wait for the webhook, and it becomes successful.
curl -X POST "https://finaldatasub.com/api/v1/merchant/data/purchase" \
-H "Authorization: Bearer ds_test_YOUR_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{"product_id": 212, "phone": "08030000003", "reference": "TEST-ORDER-1001"}'Live vs sandbox
| Live (ds_live_) | Sandbox (ds_test_) | |
|---|---|---|
| Needs API approval | Yes | No |
| Wallet | Your real wallet | Separate test wallet (₦100,000, resettable) |
| Catalogue, plan ids and prices | Real | Real (same as live) |
| Validation and error codes | Real | Same as live |
| Sent to networks / billers | Yes | Never. Tokens, PINs and eSIMs are fake |
| Webhooks | mode: "live" | Same URL, mode: "test" and data.sandbox: true |
| Transactions | GET /transactions | GET /transactions shows sandbox transactions only, kept for 30 days |
| Daily and account-tier limits | Applied | Not applied |
Telling sandbox responses apart
- Every response has the header
X-Api-Mode: test(live keys getX-Api-Mode: live). - Transaction objects, balances and verify responses include
"sandbox": true. - Sandbox
transaction_idvalues start withSBX. - Webhooks carry
mode: "test"in the body and theX-Datason-Mode: testheader.
Good to know
- Bulk airtime and data orders are processed straight away in the sandbox. Fetch them with GET /bulk/{reference}, using your reference rather than a numeric id.
- Legacy MSORG / Adex routes (
/api/data,/api/topup,/api/user) also work with a sandbox key sent asAuthorization: Token ds_test_…. - Webhook settings, beneficiaries and IP whitelists are shared between live and sandbox keys.
- Rate limits are the same as live: 120 requests per minute per key.
- If the platform turns the sandbox off, sandbox keys get
403 SANDBOX_DISABLED.