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

<div class="endpoint"><span class="method post">POST</span><code>https://api.payd.money/api/v2/payments/payout</code></div>

## Before you start

- Your balance must cover the amount plus the fee. Check both with a [quote](./quotes.md) using `operation_type` `withdrawal`.
- 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 account` until they do.
- Payd pays out immediately. Check the number with your customer first: Payd cannot pull back a payout that has arrived.

## Request

::: code-group

```bash [cURL]
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 [Node.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 eW
```

```python [Python]
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 eW
```

```php [PHP]
<?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](./limits-and-settlement.md). |
| `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](./authentication.md#find-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](./webhooks.md). |
| `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](./webhooks.md). 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](./errors.md) for every code.
