Skip to content

Transfers & Payouts

Send money out, what it leaves from, and what has to be true first.

This section covers money going out: to a supplier, a driver, a seller on your marketplace, or to your own account. A payout spends funds you already hold, which is why it passes through more checks than anything else on the API.

POST /transferschecksall clearedben_3Vt8pQ…ben_7Kq2mX…received 10 000available1 250 000 XAFpending75 000 XAFreserved0 XAF10 100 XAF
  1. 1Only available can leave

    Pending is collected but not settled, reserved is already committed. A payout draws on neither.

  2. 2Checks come first

    Each answers with its own 422. Nothing is reserved until all of them pass, so a refusal leaves nothing behind.

  3. 3The fee is held too

    Sending 10 000 XAF reserves 10 100. The 1 % is charged on completion, but it is held from the start.

A payout leaves a balance you already hold. A series of checks runs first, and only once they all pass does the amount plus its fee move out of what is available and into what is reserved. The beneficiary receives the amount, the fee stays with the rail.

What you can use

What you needWhere
Pay one person or businessTransfers
Keep a list of people you pay oftenBeneficiaries
Know what is available to send, and what has already been settledBalance & settlements
Pay many people at oncePOST /transfers/batch, up to 500 lines per call
Pay the same person on a scheduleSettings, Transfers, Automation, from the Growth plan

A batch takes up to 500 payouts in one call, on every plan. Its Idempotency-Key header is required, and each line carries a reference unique within the batch. Every line passes the same checks as a single payout, the currency's bounds included, and names its beneficiary by id only: a phone number or an e-mail is refused with beneficiary_not_found. Each line succeeds or fails on its own, so one seller without a verified destination does not stop the others.

Two things refuse a batch whole, before any line runs. A malformed line, such as a missing field, a currency that is not three letters or a reference repeated in the batch, answers 422 for the whole call, with the faulty line named in errors (transfers.3.amount). The other is a missing or empty Idempotency-Key header. Keep that key to letters, digits and . _ : -.

A recurring transfer pays one beneficiary a fixed amount every hour, day, week or month. Each run creates an ordinary payout, through every check below, and a paused schedule can be resumed.

Where the money leaves from

Wajub holds a balance per currency for your account. Collections land in it, payouts draw from it, and nothing reaches a recipient that the balance did not cover first.

GEThttps://api.wajub.com/balance

One call answers for the environment your key belongs to, and splits the money into figures that do not mean the same thing.

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

available is the only figure a payout can draw on. pending is money collected but not yet settled to you, and reserved is money already committed to payouts in flight. Pass ?currency= to read the same balance converted into another currency.

What a payout costs

On the Wajub rail, a payout is charged from 1 %, depending on the country and the channel, and your plan can lower that rate. A payout to another Wajub account (wajub) is free, and a bank transfer (wire) carries a fixed fee instead of a rate, see Fees & pricing. The fee is charged when the transfer completes, but it is held with the principal at creation: sending 10 000 XAF reserves 10 100 XAF, not 10 000.

Routing to your own provider account changes that. A payout that leaves through a provider you brought yourself is not funded from your Wajub balance, so it carries no Wajub fee and reserves nothing. The provider bills you directly on its own terms.

What has to be true before a payout executes

This is the part worth reading before your first live transfer. Creating one runs through a series of checks, each of which answers 422 with its own message and its own error_code rather than a generic failure. Branch on the code; the full list is in Errors.

CheckWhat it looks aterror_code
Amount boundsThe minimum and maximum configured for that currencyamount_out_of_range
The beneficiaryResolvable on your account, active, and carrying a payment methodbeneficiary_not_found, beneficiary_inactive, beneficiary_missing_payment_method
The channelA payout channel: Mobile Money, a Djamo wallet or another Wajub account through the API, and a bank account through a payout accountchannel_not_supported
Payouts paused platform-widePayouts suspended across Wajub, for everyonepayouts_suspended
Account restrictionsAn active transfers restriction on your accountmerchant_restricted
Negative balanceNo payout leaves while available is below zero, from any currencynegative_balance
Sync Lite reserveA Lite merchant's payout cannot dip into the slice held against refundsreserve_held
Destination verificationThe payment method has been verifiedbeneficiary_not_verified
Destination cooling-offA destination added or changed moments ago cannot be paid until its payout_allowed_after has passedbeneficiary_cooling_off
Your own daily capThe count and the amount you set in Transfer security settings. Failed and cancelled payouts do not countdaily_count_limit_exceeded, daily_amount_limit_exceeded
The platform ceilingA hard limit by KYC tier, which you cannot raise yourselfsingle_payout_limit_exceeded, daily_payout_limit_exceeded, monthly_payout_limit_exceeded, payouts_not_enabled
VelocityToo many payouts, or too large an amount paid out, in a short timetoo_many_payouts, hourly_payout_amount_exceeded
Channel ceilingThe per-transfer limit of that operatoramount_above_channel_limit
Provider availabilityA payout route exists for that channel and currencyno_provider
Fundsavailable covers the amount plus the fee, on a Wajub routeinsufficient_balance

The order matters less than the principle: nothing is reserved until every check has passed, and a transfer that fails one of them never existed. One thing does outlive the refusal: a beneficiary you passed inline, as an object, is created before the checks run. It stays on your account and in GET /beneficiaries, and beneficiary.created is sent for it. A retry with the same channel and number pays that same beneficiary, under the name it was first created with, rather than creating a second one.

The two thresholds that are yours live under Settings, Transfers: manual confirmation, with an optional amount below which it does not apply, on the General tab, and the review amount and the daily count and amount caps on the Security tab. A hold they trigger is still resolved by Wajub, not from your own Dashboard.

The same Security tab carries two switches that apply to payouts started from the Dashboard, on every plan. To protect API payouts, use restricted keys and an API IP allow-list. Require 2FA refuses a payout unless the member entered a security verification code and passed their second factor in the last 30 minutes. IP whitelist refuses every payout as long as the team has no Dashboard IP list: the list itself lives in team security, and this switch only makes sure it exists. Both settings are edited in live, by the owner or a member with manage_settings.

Creating one

POSThttps://api.wajub.com/transfers

A transfer takes an amount, a currency, and a recipient. The recipient is either a beneficiary you registered earlier, passed as its id, or the details themselves, in which case the beneficiary is created as part of the call.

curl https://api.wajub.com/transfers \
  -H "Authorization: sk.xxxxxxxxxxxxxxxx…" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: payout-892" \
  -d '{
    "beneficiary": "ben_7Kq2mX9vL4tRb3nP8sZc",
    "amount": 10000,
    "currency": "XAF",
    "description": "Supplier payment #892"
  }'

The answer is 201 Created and the transfer comes back as processing, already on its way. Both environments behave the same way here: a sandbox transfer draws on your sandbox balance and settles itself a couple of seconds later, with the outcome decided by the recipient's test number.

There is no cancel

The API creates, lists and retrieves transfers. It does not cancel them. Once a transfer is created it runs to succeeded or failed, and the only thing that pauses one is a review hold, which you do not control. Treat POST /transfers as irreversible and put your checks before it, not after.

The resources

MethodEndpointFor
POST GET/transfersCreate, list and retrieve a payout
POST GET PUT DELETE/beneficiariesKeep the people you pay
GET/balanceWhat is available to send
POST/transfers/batchPay up to 500 people in one call
POST/beneficiaries/{id}/verifyCheck a wallet belongs to the name you expect

To check the name on a wallet, use POST /beneficiaries/{id}/verify: it asks the operator in live mode and returns what it holds, for information. Checking the name on a wallet

All of them need the secret key. A public key is refused, and so is a browser: payout routes are the most tightly held on the API. If you are paying out on behalf of a connected account, the X-Sync header selects it, which Sync covers.

What did you think of this content?