Appearance
Pay out with M-Pesa in Kenya
Send money from your Payd balance to an M-Pesa number. One request pays out. There is no approval step for API payouts.
POST
https://api.payd.money/api/v2/payments/payoutBefore 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 Money App. Payouts fail with
withdrawal pin not set for accountuntil they do. - Payd pays out immediately. Check the number with your customer first: Payd cannot pull back a payout that has arrived.
Request
bash
curl https://api.payd.money/api/v2/payments/payout \
-u "$PAYD_API_USERNAME:$PAYD_API_PASSWORD" \
-H "Content-Type: application/json" \
-d '{
"amount": 2500,
"currency": "KES",
"country": "KE",
"transaction_channel": "mobile",
"customer": {
"first_name": "Joseph",
"last_name": "Otieno",
"phone_number": "254733112233"
},
"vendor_config": { "account_id": "4f28836c-2f1e-4d9b-9a51-6f3c2b1d8e70" },
"provider_config": {},
"payment_config": {
"account_number": "254733112233",
"remark": "October commission",
"reference": "payout-1042",
"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 res = await fetch('https://api.payd.money/api/v2/payments/payout', {
method: 'POST',
headers: { Authorization: `Basic ${auth}`, 'Content-Type': 'application/json' },
body: JSON.stringify({
amount: 2500,
currency: 'KES',
country: 'KE',
transaction_channel: 'mobile',
customer: { first_name: 'Joseph', last_name: 'Otieno', phone_number: '254733112233' },
vendor_config: { account_id: process.env.PAYD_ACCOUNT_ID },
provider_config: {},
payment_config: {
account_number: '254733112233',
remark: 'October commission',
reference: 'payout-1042',
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}`)
const reference = body.gateway_response.merchant_reference // ends in eWpython
import os
import requests
res = requests.post(
"https://api.payd.money/api/v2/payments/payout",
auth=(os.environ["PAYD_API_USERNAME"], os.environ["PAYD_API_PASSWORD"]),
json={
"amount": 2500,
"currency": "KES",
"country": "KE",
"transaction_channel": "mobile",
"customer": {"first_name": "Joseph", "last_name": "Otieno", "phone_number": "254733112233"},
"vendor_config": {"account_id": os.environ["PAYD_ACCOUNT_ID"]},
"provider_config": {},
"payment_config": {
"account_number": "254733112233",
"remark": "October commission",
"reference": "payout-1042",
"callback_url": "https://example.com/webhooks/payd",
},
},
timeout=35,
)
body = res.json()
res.raise_for_status()
reference = body["gateway_response"]["merchant_reference"] # ends in eWphp
<?php
$payload = [
'amount' => 2500,
'currency' => 'KES',
'country' => 'KE',
'transaction_channel' => 'mobile',
'customer' => ['first_name' => 'Joseph', 'last_name' => 'Otieno', 'phone_number' => '254733112233'],
'vendor_config' => ['account_id' => getenv('PAYD_ACCOUNT_ID')],
'provider_config' => new stdClass(),
'payment_config' => [
'account_number' => '254733112233',
'remark' => 'October commission',
'reference' => 'payout-1042',
'callback_url' => 'https://example.com/webhooks/payd',
],
];
$ch = curl_init('https://api.payd.money/api/v2/payments/payout');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_USERPWD => getenv('PAYD_API_USERNAME') . ':' . getenv('PAYD_API_PASSWORD'),
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode($payload),
CURLOPT_TIMEOUT => 35,
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);Fields
| Field | Type | Required | Description |
|---|---|---|---|
amount | number | Yes | Amount the recipient gets, in whole shillings. Today the range is 10 to 150,000. The API is the source of truth: see limits. |
currency | string | Yes | KES. |
country | string | No | KE. |
transaction_channel | string | Yes | mobile. |
customer.first_name, customer.last_name | string | Yes | Recipient's name. |
customer.phone_number | string | Yes | Recipient's M-Pesa number in the form 254733112233. |
vendor_config.account_id | string | Yes | Your account id. |
provider_config | object | Yes | Send {}. |
payment_config.account_number | string | Yes | The M-Pesa number to pay, in the same form. |
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
json
{
"gateway_response": {
"success": true,
"merchant_reference": "SID131831221eW",
"checkout_request_id": "AG_20261016_0102b3c4d5e6"
}
}success means Payd took the money from your balance and sent the payout. It does not mean the recipient has it yet.
Confirm the payout
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:
bash
curl https://api.payd.money/api/v2/payments/SID131831221eW/status \
-u "$PAYD_API_USERNAME:$PAYD_API_PASSWORD"Most M-Pesa payouts finish within a minute. Check every 15 seconds for the first two minutes, then every few minutes. If a payout fails, Payd puts the money back in your balance.
Common errors
error_code | Message | Fix |
|---|---|---|
insufficient_balance | insufficient balance | Top up your balance or pay out less. |
invalid_argument | withdrawal pin not set for account... | Set a withdrawal PIN in the Payd Money App. |
invalid_form | the network's reason | The payout was refused and the money is back in your balance. Check the number. |
See errors for every code.

