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.
1Only available can leave
Pending is collected but not settled, reserved is already committed. A payout draws on neither.
2Checks come first
Each answers with its own 422. Nothing is reserved until all of them pass, so a refusal leaves nothing behind.
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.
What you can use
| What you need | Where |
|---|---|
| Pay one person or business | Transfers |
| Keep a list of people you pay often | Beneficiaries |
| Know what is available to send, and what has already been settled | Balance & settlements |
| Pay many people at once | POST /transfers/batch, up to 500 lines per call |
| Pay the same person on a schedule | Settings, 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.
https://api.wajub.com/balanceOne call answers for the environment your key belongs to, and splits the money into figures that do not mean the same thing.
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.
The balance is not real time
A payout you created a minute ago may not have moved available yet. Do not use it as a running
total inside a loop: check it once before a batch, then trust the transfer calls themselves, which
fail on insufficient funds rather than overdrawing.
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.
| Check | What it looks at | error_code |
|---|---|---|
| Amount bounds | The minimum and maximum configured for that currency | amount_out_of_range |
| The beneficiary | Resolvable on your account, active, and carrying a payment method | beneficiary_not_found, beneficiary_inactive, beneficiary_missing_payment_method |
| The channel | A payout channel: Mobile Money, a Djamo wallet or another Wajub account through the API, and a bank account through a payout account | channel_not_supported |
| Payouts paused platform-wide | Payouts suspended across Wajub, for everyone | payouts_suspended |
| Account restrictions | An active transfers restriction on your account | merchant_restricted |
| Negative balance | No payout leaves while available is below zero, from any currency | negative_balance |
| Sync Lite reserve | A Lite merchant's payout cannot dip into the slice held against refunds | reserve_held |
| Destination verification | The payment method has been verified | beneficiary_not_verified |
| Destination cooling-off | A destination added or changed moments ago cannot be paid until its payout_allowed_after has passed | beneficiary_cooling_off |
| Your own daily cap | The count and the amount you set in Transfer security settings. Failed and cancelled payouts do not count | daily_count_limit_exceeded, daily_amount_limit_exceeded |
| The platform ceiling | A hard limit by KYC tier, which you cannot raise yourself | single_payout_limit_exceeded, daily_payout_limit_exceeded, monthly_payout_limit_exceeded, payouts_not_enabled |
| Velocity | Too many payouts, or too large an amount paid out, in a short time | too_many_payouts, hourly_payout_amount_exceeded |
| Channel ceiling | The per-transfer limit of that operator | amount_above_channel_limit |
| Provider availability | A payout route exists for that channel and currency | no_provider |
| Funds | available covers the amount plus the fee, on a Wajub route | insufficient_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.
Your first live payout does not execute immediately
A payout is held for review, rather than sent, when it is one of your account's first live
payouts, when its amount crosses a threshold you set yourself, or when your account asked for
manual confirmation of its own payouts. Wajub can also hold a payout for its own checks. A held
transfer is created with status review, its funds are reserved, and it waits for Wajub to
resolve it. Plan the first one, and do not run a launch
through an unattended loop.
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
https://api.wajub.com/transfersA 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
| Method | Endpoint | For |
|---|---|---|
POST GET | /transfers | Create, list and retrieve a payout |
POST GET PUT DELETE | /beneficiaries | Keep the people you pay |
GET | /balance | What is available to send |
POST | /transfers/batch | Pay up to 500 people in one call |
POST | /beneficiaries/{id}/verify | Check 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.
Related pages
- TransfersThe payout itself: statuses, routing, and what a failure means.
- Transfers QuickstartSend your first payout in sandbox, then live.
- BeneficiariesRegister a recipient once and pay them by id.
- Balance & settlementsWhat is available, what is pending, and what the fees were.
- IdempotencyThe header that makes a retried payout safe.