# Collect with M-Pesa in Kenya

Send an M-Pesa prompt to your customer's phone. They enter their M-Pesa PIN, and the money lands in your Payd account.

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

## How it works

1. You send the amount and the customer's phone number.
2. Payd answers with a reference, and the customer's phone shows an M-Pesa prompt.
3. The customer enters their PIN, or cancels. Prompts expire after about a minute.
4. Payd posts the result to your `callback_url`, and the status endpoint shows `success` or `failed`.

## Request

::: code-group

```bash [cURL]
curl https://api.payd.money/api/v2/payments/payin \
  -u "$PAYD_API_USERNAME:$PAYD_API_PASSWORD" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 1500,
    "currency": "KES",
    "country": "KE",
    "transaction_channel": "mobile",
    "customer": {
      "first_name": "Amina",
      "last_name": "Wanjiru",
      "phone_number": "254712345678",
      "email": "amina@example.com"
    },
    "vendor_config": { "account_id": "4f28836c-2f1e-4d9b-9a51-6f3c2b1d8e70" },
    "provider_config": {},
    "payment_config": {
      "account_number": "254712345678",
      "remark": "Order 8842",
      "reference": "order-8842",
      "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/payin', {
  method: 'POST',
  headers: { Authorization: `Basic ${auth}`, 'Content-Type': 'application/json' },
  body: JSON.stringify({
    amount: 1500,
    currency: 'KES',
    country: 'KE',
    transaction_channel: 'mobile',
    customer: { first_name: 'Amina', last_name: 'Wanjiru', phone_number: '254712345678' },
    vendor_config: { account_id: process.env.PAYD_ACCOUNT_ID },
    provider_config: {},
    payment_config: {
      account_number: '254712345678',
      remark: 'Order 8842',
      reference: 'order-8842',
      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 // store this with your order
```

```python [Python]
import os
import requests

res = requests.post(
    "https://api.payd.money/api/v2/payments/payin",
    auth=(os.environ["PAYD_API_USERNAME"], os.environ["PAYD_API_PASSWORD"]),
    json={
        "amount": 1500,
        "currency": "KES",
        "country": "KE",
        "transaction_channel": "mobile",
        "customer": {"first_name": "Amina", "last_name": "Wanjiru", "phone_number": "254712345678"},
        "vendor_config": {"account_id": os.environ["PAYD_ACCOUNT_ID"]},
        "provider_config": {},
        "payment_config": {
            "account_number": "254712345678",
            "remark": "Order 8842",
            "reference": "order-8842",
            "callback_url": "https://example.com/webhooks/payd",
        },
    },
    timeout=35,
)
body = res.json()
res.raise_for_status()
reference = body["gateway_response"]["merchant_reference"]  # store this with your order
```

```php [PHP]
<?php
$payload = [
    'amount' => 1500,
    'currency' => 'KES',
    'country' => 'KE',
    'transaction_channel' => 'mobile',
    'customer' => ['first_name' => 'Amina', 'last_name' => 'Wanjiru', 'phone_number' => '254712345678'],
    'vendor_config' => ['account_id' => getenv('PAYD_ACCOUNT_ID')],
    'provider_config' => new stdClass(),
    'payment_config' => [
        'account_number' => '254712345678',
        'remark' => 'Order 8842',
        'reference' => 'order-8842',
        'callback_url' => 'https://example.com/webhooks/payd',
    ],
];

$ch = curl_init('https://api.payd.money/api/v2/payments/payin');
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);

$reference = $body['gateway_response']['merchant_reference'] ?? null; // store this with your order
```

:::

### Fields

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `amount` | number | Yes | Amount in KES, in whole shillings. Today the range is 1 to 250,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` | string | Yes | Customer's first name. |
| `customer.last_name` | string | Yes | Customer's last name. |
| `customer.phone_number` | string | Yes | M-Pesa number in the form `254712345678`. Payd also accepts `0712345678` and `+254712345678`. |
| `customer.email` | string | No | Customer's email. |
| `vendor_config.account_id` | string | Yes | Your account id. See [authentication](./authentication.md#find-your-account-id). |
| `provider_config` | object | Yes | Send `{}`. |
| `payment_config.account_number` | string | No | The number to prompt, if it is not `customer.phone_number`. |
| `payment_config.remark` | string | No | A short note kept with the payment. |
| `payment_config.reference` | string | No | Your own id for the payment. Payd returns it as `external_reference` in webhooks. |
| `payment_config.callback_url` | string | No | Where Payd posts the result. See [webhooks](./webhooks.md). |
| `wallet_type` | string | No | `fiat` (default) or `crypto`. See [wallets](./core-concepts.md#wallets). |

## Response

```json
{
  "gateway_response": {
    "success": true,
    "merchant_reference": "9BD103350408eR",
    "checkout_request_id": "7c1e9a52d4b8"
  }
}
```

| Field | Meaning |
| --- | --- |
| `success` | `true` when the prompt was sent. It does not mean the customer has paid. |
| `merchant_reference` | Payd's reference for the payment. Store it, and use it to [check the status](./transaction-status.md). |
| `checkout_request_id` | The network's id for the prompt. Store it to help support find the payment. |
| `message` | Text about the request. Do not parse it. |

If `success` is `false`, the prompt was not sent. Show `message` in your logs and let the customer try again.

## Confirm the payment

Wait for the [webhook](./webhooks.md), then confirm with the [status endpoint](./transaction-status.md):

```bash
curl https://api.payd.money/api/v2/payments/9BD103350408eR/status \
  -u "$PAYD_API_USERNAME:$PAYD_API_PASSWORD"
```

If the customer ignores the prompt, the payment ends as `failed`. Ask them to try again, which sends a new prompt with a new reference.

## Tips for a good checkout

- Tell the customer to keep their phone unlocked and watch for the prompt.
- Show a "Waiting for your M-Pesa PIN" screen with a button to send the prompt again. Send a new prompt only after the first one has `failed`.
- If the customer does not have a name on file with you, ask for one: both names are required.

## The older collection endpoint

Integrations built before October 2026 use `POST /api/v2/payments`. It still works, and new integrations should use the endpoint above.

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

```bash
curl https://api.payd.money/api/v2/payments \
  -u "$PAYD_API_USERNAME:$PAYD_API_PASSWORD" \
  -H "Content-Type: application/json" \
  -d '{
    "channel": "MPESA",
    "amount": 1500,
    "phone_number": "0712345678",
    "narration": "Order 8842",
    "currency": "KES",
    "callback_url": "https://example.com/webhooks/payd"
  }'
```

| Field | Required | Description |
| --- | --- | --- |
| `channel` | Yes | `MPESA`. |
| `amount` | Yes | Amount in KES, from 10 to 250,000. |
| `phone_number` | Yes | Kenyan number: `0712345678`, `254712345678` or `+254712345678`. |
| `narration` | Yes | A short note about the payment. |
| `currency` | Yes | `KES`. |
| `callback_url` | Yes | Where Payd posts the result. |
| `username` | No | Older integrations send their Payd username here. Payd ignores it and uses the account of your API key. |

```json
{
  "payment_method": "mobile",
  "status": "SUCCESS",
  "transaction_reference": "9BD103350408eR",
  "success": true,
  "message": "Processed successfully",
  "trackingId": "",
  "reference": "",
  "result": null
}
```

`"status": "SUCCESS"` here means the prompt was sent, not that the customer paid. Errors on this endpoint use the [older error body](./errors.md#the-older-collection-endpoint), and webhooks can come in [either format](./webhooks.md#the-older-format).
