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.
| Number | Scenario | What happens | Final status |
|---|---|---|---|
263771000001 | Success | Customer approves promptly | completed |
263771000002 | PIN declined | Customer rejects the prompt | failed |
263771000003 | Customer timeout | Prompt delivered, never answered | expired |
263771000004 | Insufficient funds | Wallet can’t cover it | failed |
263771000005 | Invalid number | Provider rejects the MSISDN | failed |
263771000006 | Duplicate reference | Provider claims it has seen this reference | failed |
263771000007 | Delayed answer | Provider answers “pending” twice, confirms on the third query | completed |
263771000008 | Callback never arrives | Success, but the provider never calls back — our resolver discovers it by asking | completed |
263771000009 | Provider HTTP 500 | The charge call itself errors — payment goes unknown, resolves on query | unknown → completed |
263771000010 | Connection timeout | Nothing ever answers — the full unknown journey | unknown → unresolved |
263771000011 | OTP success | Two-step (O’mari-style): status awaiting_otp; confirm with OTP 123456 | completed |
263771000012 | OTP declined | awaiting_otp, then the OTP is always rejected | failed |
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:
- The happy path (
...01): create → webhook → fulfil. - A definitive failure (
...02): your UI says “declined”, your books say nothing owed. - The delayed answer (
...07): your code waits for the webhook rather than timing out at 5 seconds and guessing. - The
unknownpath (...09): your UI says “confirming — don’t pay again”, not “failed”. This is the test that protects your customers from double charges. - 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:
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.