Quickstart
Fund a sandbox balance, send your first payout, then do the same for real.
Fund a sandbox balance, send a first payout and test its failures, then switch to live. A payout spends funds you already hold, so the first step collects money.
A sandbox balance starts at zero
There is no endpoint that tops up a sandbox balance, no button in the Konsole, and no starting
credit. You fund it exactly the way you fund the live one, by collecting a payment. Skip step 1 and
step 3 answers 422 Insufficient sandbox balance to send 5000 XAF.
1. Put money in the balance
Create a sandbox payment for more than you intend to send, then pay it yourself on its checkout page. Accept a payment covers this call properly; here it is only a means to an end.
curl https://api.wajub.com/payments \
-H "Authorization: sk_test.xxxxxxxxxxxxxxxx…" \
-H "Content-Type: application/json" \
-d '{
"amount": 20000,
"currency": "XAF",
"email": "you@example.com",
"description": "Funding my sandbox balance"
}'Open the authorization_url, pick Mobile Money, and pay with +237670000000. That number always
succeeds in sandbox. A few seconds later the payment settles and the amount reaches your balance,
minus the sandbox platform fee of 2 %: 20 000 collected leaves 19 600 to send.
2. Check what you can send
One call, no parameters. It answers with the balance of the environment your key belongs to, so the same line of code reads the sandbox wallet today and the real one tomorrow.
https://api.wajub.com/balanceThe call takes an optional currency to convert the figure into another one. Leave it out and you
get your account currency.
curl https://api.wajub.com/balance \
-H "Authorization: sk_test.xxxxxxxxxxxxxxxx…"Five figures come back.
available is the only figure a payout can draw on. Check environment if you ever doubt which key
you used.
3. Send the payout
https://api.wajub.com/transfersPass the recipient inline and Wajub creates the beneficiary along with the payout, which is what you
want for a first try. The channel is the operator that serves the number, and the phone number has
to be a sandbox test number or the call is refused before anything is created.
Send an Idempotency-Key from the very first call. It costs nothing and it is the only thing that
makes a retry safe, here and in production.
curl https://api.wajub.com/transfers \
-H "Authorization: sk_test.xxxxxxxxxxxxxxxx…" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: first-payout-01" \
-d '{
"amount": 5000,
"currency": "XAF",
"description": "First test payout",
"beneficiary": {
"name": "Aminata Diallo",
"channel": "cm.mtn",
"phone": "+237670000000"
}
}'Wajub answers 201 Created and the payout is already processing. Nothing is waiting for you to
confirm it.
Store transfer.id. It is the one handle that is unique: the reference you can send comes back as
you sent it but is never checked for uniqueness, and no call cancels a payout once it exists.
Your balance has moved twice. The 5 000 left it, and so did a sandbox payout fee of 2 %, charged on top rather than taken out of the amount. 19 600 becomes 14 500, and the recipient still receives the full 5 000.
4. Watch it resolve
About two seconds later the payout finishes, and the recipient's number decided the outcome long before that. The honest way to find out is the webhook, not a loop.
| Event | Fires when |
|---|---|
transfer.created | The payout was recorded |
transfer.processing | It was handed to the operator |
transfer.succeeded | The money arrived. This is the one to act on |
transfer.failed | It did not, and failure_reason says why |
Statuses & webhooks covers registration, signatures and what belongs in each handler. While you are still exploring, read the payout back instead.
curl https://api.wajub.com/transfers/po_test_CSUGajfv9xh0XQ5wu2lx \
-H "Authorization: sk_test.xxxxxxxxxxxxxxxx…"status is now succeeded and complete is true. Those two are not the same thing: complete
means it worked, not that it is over, so a failed payout is finished and still reports false.
POST /transfers
- Authorization
- sk_test.••••••••••••eU3i
- Content-Type
- application/json
- Idempotency-Key
- first-payout-01
- channel
- cm.mtn
- provider.selected
- MTN MoMo
- idempotency
- first-payout-01
Sent
{
"amount": 5000,
"currency": "XAF",
"beneficiary": {
"name": "Aminata Diallo",
"channel": "cm.mtn",
"phone": "+237670000000"
}
}Received, 201 from MTN MoMo
{
"code": 201,
"status": "Created",
"transfer": {
"id": "po_test_CSUGajfv9xh0XQ5wu2lx",
"status": "processing"
}
}5. Break it on purpose
A payout that always succeeds teaches you nothing about the day one does not. The ending of the recipient's number picks the outcome, so run the same call again with each of these before you trust your handler.
| Number | What you get |
|---|---|
+237670000000 | succeeded |
+237670000001 | 422 at creation, error_code: insufficient_funds. No payout and no transfer webhook |
+237670000002 | failed, failure_reason: failure |
+237670000003 | failed, failure_reason: network_error |
+237670000004 | failed, failure_reason: invalid_recipient |
Only the ending matters, not the operator, so the same five work behind any prefix the sandbox knows. A number that matches none of them is refused outright.
Point your failure handler at …0002, not …0001
…0001 is a recipient whose wallet cannot take the money, and the sandbox refuses the call rather
than creating a payout that fails. There is no transfer and no transfer event, so a handler tested
against it is never actually called. Use …0002 for that.
A refused payout still creates its inline beneficiary
The beneficiary you pass as an object is created before the checks run, so a refused call, …0001
included, leaves it on your account, listed by GET /beneficiaries, and sends
beneficiary.created as any new beneficiary does. Running the same call again with the same
channel and number reuses it rather than creating a second one.
Then go live
Swap sk_test. for sk. and four things change, none of them in your code.
| In sandbox | In live |
|---|---|
| The balance is funded by test payments, and a payout costs 2 % | It is funded by real collections, and a payout costs from 1 %, depending on the country and channel, held with the principal at creation |
| Any test number works | The number has to be real, and a wrong digit pays a stranger you cannot claw back from |
| The payout executes immediately | Your first ones can be held for review until Wajub releases them |
| Failures come from three fixed codes | They come from the operator, in its own vocabulary |
That third row is the one to plan for. A held payout is created with status review, its funds are
reserved, and nothing you can call releases it. Send your first live payout to yourself, for a small
amount, at a time when someone is watching, and never as the first iteration of a batch.
Transfers & Payouts lists every check a live payout runs through, and
Transfer reference documents the object in full.