Sandbox
What sandbox is for, how an order reaches each terminal state there, and how to receive your first webhook.
https://sandbox.onlink.africaSandbox is where you build and certify. It runs the same code as production: the same signing, the same nonce store, the same order lifecycle, the same webhook dispatcher and the same error codes. What differs is the money: buy orders, payouts, conversions and collections run on simulated money, M-PESA funding requests run on the sandbox account rather than a production one (see Simulated money in sandbox), and the amounts are small.
What you can do here
- Sign requests and get real
401s when you get it wrong. - Take quotes off a live rate source.
- Create orders on both legs and drive them to a terminal state.
- Pay one of your collections without moving real money; see Testing in sandbox.
- Send payouts and batches on test money and watch them settle; see Testing in sandbox: Payouts.
- Convert collected USD to KES on test money; see Testing in sandbox: Conversions.
- Send USDT to a registered wallet and watch it settle; see Testing in sandbox: Sending USDT to a wallet.
- Register and confirm withdrawal addresses.
- Receive real, signed webhooks at your own endpoint.
Simulated money in sandbox
In sandbox, buy orders, transfers, batch transfers, conversions and collections run on simulated money, unless we enable real money for your account (which applies to orders, transfers and conversions; collections stay simulated). M-PESA funding requests are not simulated yet. They run on the real sandbox account, as they always did.
Where each flow reads and writes money:
- Buy orders are debited from a simulated KES balance, and
GET /v1/balancesreports that simulated figure. The order reachesfunds_reservedand then confirms its debit as it does in production. - Collections credit a simulated USD balance, always, with or without
real money enabled for your account. A Collections credential reads it as
usdonGET /v1/balanceswhile your account is simulated. - Transfers and batch transfers are paid from the same simulated KES balance (a USD payout from the simulated USD balance), are checked against it, and settle by themselves with the usual webhooks; see Testing in sandbox: Payouts.
- Conversions take USD from the simulated USD balance and pay KES into the simulated KES balance; see Testing in sandbox: Conversions.
- M-PESA funding requests use the real sandbox account.
- A sell order's KES payout is not simulated yet and does not credit the simulated balance.
Two balances, one account number. The accountNumber on
GET /v1/balances is a real account at the bank, and it holds a real sandbox
balance beside your simulated one. A payment to that number from outside, and
an M-PESA funding request, add to the real balance. They do not change the
simulated balance that buy orders and transfers use, so the figure on
GET /v1/balances does not move.
Funding a buy or a payout in sandbox. The simulated balance starts at zero and is not funded by payments. To fund buy orders and payouts, email info@onlink.africa with the amount you want added and we will add it to your simulated KES balance.
Funding a USDT send in sandbox. If your wallet is one we custody
(onlink_managed), a USDT send is
paid from your sandbox USDT balance, which has to hold the amount when you
confirm the send. A settled buy adds to it. To get one without a buy, email
info@onlink.africa with the amount you want added
and we will add it to your sandbox USDT balance. The send then settles on its own;
see Testing in sandbox: Sending USDT to a wallet.
Real money, on request. We can switch real money on for your account. It
applies to the buy and sell orders, the transfers and the conversions you create
after the switch. After it, GET /v1/balances reports the real balances.
A flow keeps the mode it started in. An order, a transfer or payout, a batch, a conversion or a collection created while your account was simulated stays simulated to its end, and one created on real money stays real, whatever we change afterwards. That includes every step of a conversion, and the return of your USD if it fails.
Production is unaffected: simulated money exists only in sandbox.
Driving an order to a terminal state
This is the question that stalls most evaluations on day two, so here is the honest answer for each ending.
| Ending | How to get there in sandbox |
|---|---|
expired | Only a sell whose USDT was never taken can expire, so not one whose collection we have recorded: it is held for us. Leave a sell your create refused after writing the order (a 503 such as WALLET_OWNERSHIP_CHECK_FAILED) past its 24-hour window and a sweep marks it shortly after. A buy is funded when created and does not expire; a sell paid from your USDT balance is debited at create, normally leaves awaiting_usdt at once, and is never expired once debited. |
settled | Create the order — it is funded at creation, from your KES balance on a buy and your USDT balance on a sell — then ask us to release it. Sandbox settlement is not self-serve. A sell's KES payout does not complete in sandbox yet: its order is held for review, so a sell cannot reach settled here today (a buy can). |
rejected | Ask us to reject an order you have created. |
No sell order needs a simulated USDT receipt today. When the USDT comes from your
balance, it is taken when you create the order, so
POST /v1/simulations/orders/{id}/usdt-receipt
answers 409 ORDER_NOT_AWAITING_USDT. When it comes from your own wallet, we
collect it, the sandbox completes the collection within about a minute of the
transfer completing and the order moves on from awaiting_usdt, so that call
answers 409 ORDER_USDT_COLLECTED_AUTOMATICALLY.
Paying a collection is self-serve. Both routes, and what they do not simulate
yet, are on Testing in sandbox.
Releasing and rejecting are not self-serve yet
No sandbox endpoint releases or rejects an order for you. To see
order.settled or order.rejected end to end, email
info@onlink.africa with the orderId and the
outcome you want, and we will drive it. It is a manual step and we would
rather say so than let you discover it by waiting.
order.funds_confirmed needs nothing from us, so it is the fastest way to prove
your webhook handler works: once your sandbox USDT balance can cover a sell,
create one from that balance and the event is enqueued the moment the order is
created. See
Sell USDT, receive KES.
Telling simulated money from live
Two sandbox-only routes act as if money arrived:
POST /v1/simulations/collections/{id}/deposits
and
POST /v1/simulations/orders/{id}/usdt-receipt.
Anything they produce is marked, so your code can tell it from a real payment.
Orders that run on simulated money are marked the same way, whether or not you
called a simulation route for them. The marker is the X-OnLink-Money-Mode
response header:
| Value | Meaning |
|---|---|
simulated | A sandbox simulation produced this, it is an order that ran on simulated money, or it is a USDT send made in a sandbox where simulations are enabled. No real money moved, and none is owed to you for it. |
live | None of these is true. Every read answers live outside the sandbox. |
It is set on a successful response from the two simulation routes (always
simulated), and on
GET /v1/collections/{id},
GET /v1/orders/{id} and
GET /v1/usdt/sends/{id}. A collection
reads simulated once a simulation has been run for it. An order reads
simulated when it ran on simulated money (for example a sandbox buy paid from
a simulated KES balance) or once a simulation has been run for it. A USDT send
reads simulated for every send you make while sandbox simulations are enabled,
because a sandbox send moves no real USDT, and live everywhere else. Anything
else reads live. An error response, and the list reads, carry no such header.
Receiving your first webhook
You need an HTTPS endpoint we can reach. For a first delivery a public request-bin service is fine — you are checking that a signed request arrives and that your verifier accepts it, not moving value.
- Send us the URL and we register it, along with your signing secret.
- Trigger an event — creating a sell order from your USDT balance is the simplest.
- Verify the signature with the recipe in Verifying signatures.
Move to your own endpoint before you certify. A request bin will not exercise
your deduplication on X-OnLink-Delivery, and duplicate deliveries are normal.
Amounts
Keep test amounts small. Your per-order and rolling-24-hour caps apply in sandbox exactly as they do in production, and they are set per partner — the values are in your onboarding, not on this page.
Going to production
Production access is enabled per partner once your integration is certified. Nothing in your code changes but the base URL and your credentials. Talk to us when you are ready.