Sandbox testing
Deterministic test outcomes keyed on magic MSISDNs, and on-demand callback replay.
A Sandbox-environment credential can trigger deterministic outcomes on MOMO_DEPOSIT,
MOMO_WITHDRAWAL, CRYPTO_BUY, CRYPTO_SELL, and KYC_MOMO via magic MSISDNs — the same
convention every real payment sandbox uses (Stripe's test card numbers, Twilio's magic phone
numbers, MTN's own documented test MSISDNs). The MSISDN is the identity value a real provider
actually keys outcomes on, so it's the value a test double keys on too — never an incidental
business parameter like amount, which you need to vary freely for your own testing (limit
enforcement, rounding, …).
| MSISDN | Outcome |
|---|---|
+237670000001 | Accepted; a status poll resolves it to success on the very first check |
+237670000002 | Rejected immediately with MTN's real PAYER_NOT_FOUND (money in) / PAYEE_NOT_FOUND (money out) reason |
+237670000003 | Rejected immediately with MTN's real NOT_ENOUGH_FUNDS reason |
+237670000004 | Rejected immediately with MTN's real SERVICE_UNAVAILABLE reason |
+237670000005 | Accepted; never resolves — stays Pending forever (for testing your own timeout/retry handling) |
+237670000006 | Accepted; stays Pending for ~5 seconds, then resolves to success on the next poll (for testing realistic async timing, not just instant-or-never) |
+237670000007 | KYC_MOMO only — resolves to an active account |
+237670000008 | KYC_MOMO only — resolves to an inactive/suspended account |
| anything else | Refused with 400 GW-400-BAD-REQUEST — Sandbox never reaches the real MTN network |
Sandbox never touches real money
A Sandbox credential only ever talks to the simulator. Any number that is not in the table
above is refused with a 400, for every service including KYC_MOMO (so test keys can never
look up a real customer's name).
Sandbox also has its own balance. Money from Sandbox deposits lands in your Sandbox balance
only, and Sandbox withdrawals and crypto sells draw only from it. Your Live balance is never
affected, and Sandbox money can never be paid out for real. GET /api/v1/ledger/balance and
GET /api/v1/ledger/history return the books of the credential you call them with. Daily limits
are counted per environment too, so Sandbox traffic never uses up a Live limit.
Rejection reasons use MTN Mobile Money's own real API vocabulary, not gateway-invented strings — code written against these Sandbox responses needs no rewriting for production error handling.
This registry is discoverable at runtime, so you never have to keep this table in sync by hand:
GET /api/v1/sandbox/test-scenariosreturns the exact same list, read from the identical source the simulator itself acts on.
Replaying a callback
Resolved a Sandbox transaction already and want to re-test your webhook handler without creating a
whole new transaction? Fire the callback again on demand — the same capability Stripe's CLI
(stripe trigger) and Plaid Sandbox (fire_webhook) expose:
POST /api/v1/sandbox/callbacks/{partnerOrderId}/triggerRequires a Sandbox credential (403 on Live) and a transaction that has already left Pending
(400 otherwise — there's nothing to replay for a transaction with no resolved outcome yet).
Enqueues a fresh delivery attempt the same way the real terminal-status path does, following the
usual retry/backoff rules if your endpoint doesn't acknowledge it.
A Live-environment request never simulates, regardless of which MSISDN it carries — only a
Sandbox-authenticated credential's requests are ever intercepted.
