OnLink
Get started

Sandbox

What sandbox is for, how an order reaches each terminal state there, and how to receive your first webhook.

https://sandbox.onlink.africa

Sandbox 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/balances reports that simulated figure. The order reaches funds_reserved and 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 usd on GET /v1/balances while 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.

EndingHow to get there in sandbox
expiredOnly 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.
settledCreate 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).
rejectedAsk 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:

ValueMeaning
simulatedA 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.
liveNone 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.

  1. Send us the URL and we register it, along with your signing secret.
  2. Trigger an event — creating a sell order from your USDT balance is the simplest.
  3. 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.

On this page