Skip to content

Sandbox and test numbers

Test keys (sk_test_) hit the same API against a simulated provider. No real money can move on a test key, ever — the environments are separated at the key level, not by a flag you could get wrong.

The test numbers

The last two digits of the customer number choose the scenario. Any other suffix behaves as success.

NumberScenarioWhat happensFinal status
263771000001SuccessCustomer approves promptlycompleted
263771000002PIN declinedCustomer rejects the promptfailed
263771000003Customer timeoutPrompt delivered, never answeredexpired
263771000004Insufficient fundsWallet can’t cover itfailed
263771000005Invalid numberProvider rejects the MSISDNfailed
263771000006Duplicate referenceProvider claims it has seen this referencefailed
263771000007Delayed answerProvider answers “pending” twice, confirms on the third querycompleted
263771000008Callback never arrivesSuccess, but the provider never calls back — our resolver discovers it by askingcompleted
263771000009Provider HTTP 500The charge call itself errors — payment goes unknown, resolves on queryunknown → completed
263771000010Connection timeoutNothing ever answers — the full unknown journeyunknown → unresolved
263771000011OTP successTwo-step (O’mari-style): status awaiting_otp; confirm with OTP 123456completed
263771000012OTP declinedawaiting_otp, then the OTP is always rejectedfailed

Two-step (OTP) methods

Methods like O’mari don’t push a prompt — they send the customer a one-time PIN. In the sandbox, numbers …11 and …12 exercise that flow: the charge returns status awaiting_otp, and you submit the OTP to finish. The sandbox OTP is 123456 — the right value on …11 completes the payment; any other value is declined, and …12 always declines. See Discover payment methods for which fields each method’s flow collects.

What to actually test

Before going live, walk your integration through at least:

  1. The happy path (...01): create → webhook → fulfil.
  2. A definitive failure (...02): your UI says “declined”, your books say nothing owed.
  3. The delayed answer (...07): your code waits for the webhook rather than timing out at 5 seconds and guessing.
  4. The unknown path (...09): your UI says “confirming — don’t pay again”, not “failed”. This is the test that protects your customers from double charges.
  5. A retry with the same Idempotency-Key: confirm you get the same payment back, not a second one.

Forcing outcomes without numbers

POST /v1/test/simulate (test keys only) advances a payment directly — useful in automated test suites where you don’t want to wait for the resolver’s schedule:

Terminal window
curl https://api.lango.co.zw/v1/test/simulate \
-H "Authorization: Bearer sk_test_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{ "payment_id": "pay_...", "outcome": "completed" }'

Webhooks in the sandbox

Test-mode webhooks deliver and sign exactly like live ones. Point an endpoint at a tunnel (ngrok or similar) during development, and use POST /v1/events/{id}/replay to re-fire any event while debugging your handler.