Appearance
Pay out to mobile money and banks in Africa
Send money from your Payd balance to a mobile money wallet or a bank account. For M-Pesa numbers in Kenya, see pay out with M-Pesa.
POST
https://api.payd.money/api/v2/payments/payoutWhere you can pay out
| Country | country | currency | Mobile money | Banks |
|---|---|---|---|---|
| Kenya | KE | KES | M-Pesa | Yes |
| Uganda | UG | UGX | Airtel Money, MTN MoMo | |
| Tanzania | TZ | TZS | Airtel Money, Halopesa, M-Pesa, Tigo Pesa | Yes |
| Rwanda | RW | RWF | MTN MoMo | |
| Zambia | ZM | ZMW | Airtel Money, MTN MoMo | |
| Nigeria | NG | NGN | Yes | |
| South Africa | ZA | ZAR | Yes | |
| Botswana | BW | BWP | Orange Money | Yes |
| Ethiopia | ET | ETB | Yes | |
| Côte d'Ivoire | CI | XOF | MTN MoMo, Moov Money, Orange Money, Wave | |
| Senegal | SN | XOF | Free Money, Orange Money, Wave | |
| Cameroon | CM | XAF | MTN MoMo, Orange Money | |
| Benin | BJ | XOF | MTN MoMo, Moov Money | |
| Togo | TG | XOF | Moov Money, TogoCell |
Banks, networks, limits and settlement times change, and the API is the source of truth for all of them. Read them from payment options by country before each payout. See limits and settlement.
Before you start
- Your balance must cover the amount plus the fee. Check both with a quote using
operation_typewithdrawal. - The person who owns the API key must have set a withdrawal PIN in the Payd app.
- Payouts to some countries need your account's identity details to be complete in the Payd app. If they are not, the payout fails with
Complete your KYC before sending money to this country.
1. Pick the network or bank
Fetch the options for the recipient's country, and keep the entries for payouts:
bash
curl https://api.payd.money/api/v2/payments/discovery/TZ \
-u "$PAYD_API_USERNAME:$PAYD_API_PASSWORD"Keep networks where ramp_type is withdraw and status is active. channel_type tells you the kind:
channel_type | What it is | Send transaction_channel | Send provider_config.account_type |
|---|---|---|---|
momo | A mobile money network | mobile | momo |
bank or eft | A bank | bank | bank |
Show network_name to the user. Banks are listed one per bank, so a country can have hundreds: offer a search box. Keep the chosen entry's network_id, channel_id, min_amount and max_amount.
2. Send the payout
bash
curl https://api.payd.money/api/v2/payments/payout \
-u "$PAYD_API_USERNAME:$PAYD_API_PASSWORD" \
-H "Content-Type: application/json" \
-d '{
"amount": 50000,
"currency": "TZS",
"country": "TZ",
"transaction_channel": "mobile",
"customer": {
"first_name": "Neema",
"last_name": "Mushi",
"phone_number": "255754123456",
"country": "TZ"
},
"vendor_config": { "account_id": "4f28836c-2f1e-4d9b-9a51-6f3c2b1d8e70" },
"provider_config": {
"network_id": "NETWORK_ID_FROM_DISCOVERY",
"channel_id": "CHANNEL_ID_FROM_DISCOVERY",
"account_type": "momo"
},
"payment_config": {
"account_number": "255754123456",
"remark": "October commission",
"reference": "payout-1043",
"callback_url": "https://example.com/webhooks/payd"
}
}'bash
curl https://api.payd.money/api/v2/payments/payout \
-u "$PAYD_API_USERNAME:$PAYD_API_PASSWORD" \
-H "Content-Type: application/json" \
-d '{
"amount": 150000,
"currency": "NGN",
"country": "NG",
"transaction_channel": "bank",
"customer": {
"first_name": "Chinedu",
"last_name": "Okafor",
"phone_number": "2348031234567",
"country": "NG"
},
"vendor_config": { "account_id": "4f28836c-2f1e-4d9b-9a51-6f3c2b1d8e70" },
"provider_config": {
"network_id": "BANK_NETWORK_ID_FROM_DISCOVERY",
"channel_id": "CHANNEL_ID_FROM_DISCOVERY",
"account_type": "bank"
},
"payment_config": {
"account_number": "0123456789",
"remark": "Invoice 2207",
"reference": "payout-1044",
"callback_url": "https://example.com/webhooks/payd"
}
}'js
const auth = Buffer.from(
`${process.env.PAYD_API_USERNAME}:${process.env.PAYD_API_PASSWORD}`,
).toString('base64')
const headers = { Authorization: `Basic ${auth}`, 'Content-Type': 'application/json' }
// Find the bank the user picked
const options = await fetch('https://api.payd.money/api/v2/payments/discovery/NG', { headers })
.then((r) => r.json())
const bank = Object.values(options.provider_networks ?? {})
.flatMap((group) => group.networks)
.find((n) => n.ramp_type === 'withdraw' && n.status === 'active' && n.network_name === 'Kuda Microfinance Bank')
const res = await fetch('https://api.payd.money/api/v2/payments/payout', {
method: 'POST',
headers,
body: JSON.stringify({
amount: 150000,
currency: 'NGN',
country: 'NG',
transaction_channel: 'bank',
customer: { first_name: 'Chinedu', last_name: 'Okafor', phone_number: '2348031234567', country: 'NG' },
vendor_config: { account_id: process.env.PAYD_ACCOUNT_ID },
provider_config: { network_id: bank.network_id, channel_id: bank.channel_id, account_type: 'bank' },
payment_config: {
account_number: '0123456789',
reference: 'payout-1044',
callback_url: 'https://example.com/webhooks/payd',
},
}),
signal: AbortSignal.timeout(35_000),
})
const body = await res.json()
if (!res.ok) throw new Error(`${body.error_code}: ${body.error_message}`)
console.log(body.gateway_response.merchant_reference)python
import os
import requests
AUTH = (os.environ["PAYD_API_USERNAME"], os.environ["PAYD_API_PASSWORD"])
options = requests.get("https://api.payd.money/api/v2/payments/discovery/NG", auth=AUTH, timeout=15).json()
bank = next(
n
for group in (options.get("provider_networks") or {}).values()
for n in group["networks"]
if n["ramp_type"] == "withdraw" and n["status"] == "active" and n["network_name"] == "Kuda Microfinance Bank"
)
res = requests.post(
"https://api.payd.money/api/v2/payments/payout",
auth=AUTH,
json={
"amount": 150000,
"currency": "NGN",
"country": "NG",
"transaction_channel": "bank",
"customer": {"first_name": "Chinedu", "last_name": "Okafor", "phone_number": "2348031234567", "country": "NG"},
"vendor_config": {"account_id": os.environ["PAYD_ACCOUNT_ID"]},
"provider_config": {"network_id": bank["network_id"], "channel_id": bank["channel_id"], "account_type": "bank"},
"payment_config": {
"account_number": "0123456789",
"reference": "payout-1044",
"callback_url": "https://example.com/webhooks/payd",
},
},
timeout=35,
)
res.raise_for_status()
print(res.json()["gateway_response"]["merchant_reference"])Fields
| Field | Type | Required | Description |
|---|---|---|---|
amount | number | Yes | Amount the recipient gets, in the local currency, as a whole number, within the network's limits. |
currency | string | Yes | The country's currency from the table above. |
country | string | Yes | The two-letter country code. |
transaction_channel | string | Yes | mobile or bank. |
customer.first_name, customer.last_name | string | Yes | Recipient's name. For a bank payout, the name on the bank account. |
customer.phone_number | string | Yes for mobile money | Recipient's number in international form without a +, such as 255754123456. Send it for bank payouts too when you have it. |
customer.country | string | No | The same country code. |
vendor_config.account_id | string | Yes | Your account id. |
provider_config.network_id | string | Yes | network_id of the chosen network or bank. |
provider_config.channel_id | string | Yes | channel_id of its withdraw entry. |
provider_config.account_type | string | Yes | momo or bank. See the table in step 1. |
payment_config.account_number | string | Yes | For mobile money, the wallet number without a +. For a bank, the account number. |
payment_config.reference | string | No | Your own id for the payout. |
payment_config.callback_url | string | No | Where Payd posts the result. See webhooks. |
payment_config.remark | string | No | A short note kept with the payout. |
wallet_type | string | No | Balance to pay from: fiat (default) or crypto. |
Response and confirmation
The response has the same shape as an M-Pesa payout. Store gateway_response.merchant_reference, which ends in eW.
Payd posts payout.succeeded or payout.failed to your callback_url when the payout finishes. See webhooks. If you set no callback_url, or a webhook does not arrive, check the status until it is success or failed. Mobile money usually finishes within minutes. Banks can take longer, up to the next business day. A failed payout puts the money back in your balance.
Nigeria
If your Payd account is registered in Nigeria, payouts need a second identity document, a BVN or NIN, on your account. Payd tells you with Nigeria needs a second form of identification. Email support@paydhq.com to add one. Accounts registered outside Nigeria can pay Nigerian bank accounts without it.
Common errors
error_code | Message | Fix |
|---|---|---|
insufficient_balance | insufficient balance | Top up your balance or pay out less. |
invalid_argument | Complete your KYC before sending money to this country... | Complete your identity details in the Payd app. |
invalid_form | the network's reason | The payout was refused and the money is back in your balance. Check the account number and name. |
resource_not_found | no pricing configured... | Payd does not offer this country, currency and channel together. |
See errors for every code.

