# Payments

POST /collect to trigger an STK Push and GET /status/:id to read the outcome — the two endpoints that move and read money in.

## POST /collect

Initiate an STK Push. The customer gets an M-Pesa prompt on their handset; when they enter their PIN, funds settle directly to your till. Returns immediately with a `paymentId` — the final result arrives via the hosted callback, which you read back with `GET /status/:id` or a [webhook](/docs/api/webhooks).

```text title="Endpoint"
POST https://paylod.dev/functions/v1/collect
```

### Headers

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `Authorization` | string | Required | Bearer API key (`mp_live_…` / `mp_test_…`). |
| `Content-Type` | string | Required | `application/json`. |
| `Idempotency-Key` | string | Optional | Replay guard. A repeat with the same key **and** body returns the first response. |

### Body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `amount` | integer | Required | Whole KES, 1–150000. M-Pesa rejects decimals. |
| `phone` | string | Required | Kenyan MSISDN in any common form (`0712…`, `254712…`, `+254712…`). Normalised for you. |
| `accountReference` | string | Optional | Your correlation id (invoice/order number) — returned as `accountRef` on `/status/:id` and on the webhook. Safaricom shows it to the payer only on a **Paybill** (`CustomerPayBillOnline`), where it is the account number; a **Till / Buy Goods** shortcode does not display it at all. 1–12 chars. Defaults to `collect`. |
| `description` | string | Optional | Free-text label, 1–64 chars. Defaults to `Payment`. |
| `metadata` | object | Optional | Opaque key/values stored with the payment. Never inspected, and not returned on `/status` or the webhook — key your records on `paymentId`. |

### Example

```bash title="Request"
curl -X POST https://paylod.dev/functions/v1/collect \
  -H "Authorization: Bearer mp_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order_2041" \
  -d '{
    "amount": 10,
    "phone": "254712345678",
    "accountReference": "INV-2041",
    "description": "Order #2041",
    "metadata": { "orderId": "2041" }
  }'
```

```json title="Response · 202 Accepted"
{
  "paymentId": "e69e5c00-8a01-44ed-b003-48e2e86a7c9e",
  "status": "pending",
  "checkoutRequestId": "ws_CO_010720261905029287161380"
}
```

### Errors

| Status | When |
| --- | --- |
| `401` | Missing or invalid API key. |
| `400` | Malformed JSON, or Daraja credentials not configured for this app / environment. |
| `409` | Idempotency key reused with a different body. |
| `422` | Body failed validation (e.g. `amount` ≤ 0, bad phone). |
| `429` | Rate limited — per key and per phone number. |
| `502` | Daraja rejected the STK Push (upstream error passed through). |

## GET /status/:id

Read a payment's current state — the zero-infrastructure way to get the result, with no webhook receiver needed. If the payment is still `pending`, paylod runs a live M-Pesa STK Query and settles it on the spot before responding. Scoped to the key's application: another application's payment returns `404`.

```text title="Endpoint"
GET https://paylod.dev/functions/v1/status/:id
```

### Path

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | Required | The `paymentId` returned by `POST /collect`. |

### Example

```bash title="Request"
curl https://paylod.dev/functions/v1/status/e69e5c00-8a01-44ed-b003-48e2e86a7c9e \
  -H "Authorization: Bearer mp_live_YOUR_API_KEY"
```

```json title="Response · 200 OK"
{
  "id": "e69e5c00-8a01-44ed-b003-48e2e86a7c9e",
  "status": "success",
  "mpesaReceipt": "UG1F3A1U7J",
  "resultCode": 0,
  "resultDesc": "The service request is processed successfully."
}
```

### Status values

| Value | Meaning |
| --- | --- |
| `pending` | The prompt is out; the customer has not resolved it yet. |
| `success` | Settled. `mpesaReceipt` is the M-Pesa receipt number. |
| `failed` | `resultCode` / `resultDesc` carry the decoded M-Pesa reason. |

On failure, `resultCode` is the raw Safaricom code — `1032` cancelled, `1037` timeout, `2001` wrong PIN. All of them are decoded in the [error reference](/docs/errors).
