Test mode

Build and test the entire integration: payments, payouts, refunds, webhooks and failures, before anyone verifies anything, against numbers you own.

How it works

Create an app, take its key, and use the same base URL and the same endpoints. A test-mode payment is a real payment: a prompt appears on the phone you addressed, somebody approves or declines it, and the money actually moves. The webhooks, ledger entries and dashboard rows are produced by exactly the same code that produces the live ones.

Test mode is not a mock, and it costs real money

There is no fake gateway and no play money. A test payment of 500 RWF takes 500 RWF off the phone you addressed. Use small amounts and your own numbers.

That is deliberate, and it is the opposite of how most test modes work. A simulated gateway only ever tells you what the simulator was written to believe. The states you actually spend your production life in: a prompt nobody answers, a wallet with no float, a network that times out and settles anyway: are the states a mock cannot produce.

Register your test numbers first

Test mode will only ever transact with numbers on your test list. Anything else is refused with a 400, before anything is sent.

This obstacle is the point

Because these are real payments, a fixture with a typo in it would take real money off a stranger’s phone, for an order that does not exist, from a company they have never heard of. Registering your own numbers once costs you a form field and makes that impossible.

Choosing the outcome

You choose it the way your customers will: on the phone. Approve the prompt and the payment succeeds; decline it and it fails; ignore it and it sits pending until it expires. It is a real one, so there is nothing to configure.

What you doWhat your integration sees
Approve the promptpending → succeeded, with a payment.succeeded webhook
Decline the promptpending → failed, with a payment.failed webhook
Ignore itpending until the prompt expires, then failed: minutes later, long after the customer closed the tab

The third row is the valuable one, and it is the one you cannot get from a mock: leave the prompt alone and watch what your code does with a payment.failed that arrives after the checkout page has gone.

100  → the minimum, in both modes: and a sensible test amount
     the amount does not decide the outcome; the phone does

What is identical to live

The railsThe same endpoints, the same response codes, the same timing and the same real money.
ValidationSame minimum, same number format, same refusals. A request test mode accepts is one production accepts.
WebhooksSame events, same signing, same retry schedule. Point them at a tunnel on your laptop.
IdempotencySame replay behaviour, same unique index.
Fees and ledgerCalculated and posted the same way, against a test ledger.
DashboardSame screens and filters, with the mode switch at the top and a band across the page while you are in test.

What is different

Who you may addressOnly your registered test numbers. Live mode has no such limit.
You cannot withdraw itA test balance is real money on its own books, and withdrawing is what verification gates. Get verified and go live to take money out.
Separate booksTest payments never appear in a live total, a live balance or a live report.
No feeWe take nothing on a test payment.

Going live

Get verified, create a live app, swap the key. That is the entire migration. The base URL, the request bodies, the webhook format and your handler code are unchanged, because there was never a second API to migrate from.

Flipping it is the whole migration

The switch is on the app’s own page in your dashboard, and it takes effect on the next request. Your key does not change, so there is nothing to redeploy and nothing in production to edit. Check the badge on the app rather than reading a key: the app says what mode it is in, and it is the only thing that decides.