Skip to content

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.

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.

GEThttps://api.wajub.com/balance

The 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.

Response · 200 OK
{
"code": 200,
"status": "OK",
"message": "Balance retrieved",
"balance": {
"total": 19600,
"available": 19600,
"pending": 0,
"reserved": 0,
"credit": 0,
"currency": "XAF",
"environment": "sandbox"
}
}

available is the only figure a payout can draw on. Check environment if you ever doubt which key you used.

3. Send the payout

POSThttps://api.wajub.com/transfers

Pass 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.

Response · 201 Created
{
"code": 201,
"status": "Created",
"message": "Transfer initiated",
"transfer": {
"id": "po_test_CSUGajfv9xh0XQ5wu2lx",
"amount": 5000,
"currency": "XAF",
"status": "processing",
"complete": false,
"sandbox": true,
"source": "api",
"beneficiary": {
"id": "ben_test_7Kq2mX9vL4tRb3nP8sZc",
"name": "Aminata Diallo",
"phone": "+237670000000"
},
"created_at": "2026-09-12T14:31:02+00:00"
}
}

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.

EventFires when
transfer.createdThe payout was recorded
transfer.processingIt was handed to the operator
transfer.succeededThe money arrived. This is the one to act on
transfer.failedIt 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

201690 ms
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.

NumberWhat you get
+237670000000succeeded
+237670000001422 at creation, error_code: insufficient_funds. No payout and no transfer webhook
+237670000002failed, failure_reason: failure
+237670000003failed, failure_reason: network_error
+237670000004failed, 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.

Then go live

Swap sk_test. for sk. and four things change, none of them in your code.

In sandboxIn 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 worksThe number has to be real, and a wrong digit pays a stranger you cannot claw back from
The payout executes immediatelyYour first ones can be held for review until Wajub releases them
Failures come from three fixed codesThey 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.

What did you think of this content?