# paylod: M-Pesa payments without a backend

paylod is M-Pesa (Safaricom Daraja) without the backend: one HTTP call sends the STK Push, we host the callback, sign your webhooks and decode every result code.

## What paylod is

paylod is a hosted layer over Safaricom's **Daraja** API. You keep your own till or paybill, and your own Daraja credentials. M-Pesa settles the money **straight to you**. paylod never holds the money and never takes a part of it.

paylod does the difficult work for you. You do not host a public HTTPS callback. You do not refresh access tokens. You do not retry failed STK pushes. You do not decode `resultCode: 1037`. You do not build a webhook signature scheme.

```ts tab="node" title="The whole integration"
import { Paylod } from "@paylod/node";

const paylod = new Paylod(process.env.PAYLOD_API_KEY!);

const outcome = await paylod.collectAndWait({
  amount: 100,
  phone: "0712345678",
  idempotencyKey: attempt.id,   // one key per payment attempt
});

if (outcome.paid) console.log("Paid:", outcome.receipt);
else console.log(outcome.message);   // already decoded, safe to show a customer
```
```bash tab="curl" title="The whole integration"
# collectAndWait is SDK sugar: POST /collect, then poll GET /status/:id.
curl -X POST https://paylod.dev/functions/v1/collect \
  -H "Authorization: Bearer mp_test_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: att_9f2c1b40" \
  -d '{ "amount": 100, "phone": "254712345678" }'
# → 202 { "paymentId": "...", "status": "pending" }
# then poll GET /functions/v1/status/{paymentId} until status is success or failed
```
```php tab="php" title="The whole integration"
use Paylod\Paylod;

$paylod = new Paylod($_ENV['PAYLOD_API_KEY']);

$outcome = $paylod->collectAndWait([
    'amount' => 100,
    'phone' => '0712345678',
    'idempotencyKey' => $attempt->id,   // one key per payment attempt
]);

if ($outcome->paid) {
    echo "Paid: {$outcome->receipt}";
} else {
    echo $outcome->message;             // already decoded, safe to show a customer
}
```
```python tab="python" title="The whole integration"
import os
from paylod import Paylod

paylod = Paylod(os.environ["PAYLOD_API_KEY"])

outcome = paylod.collect_and_wait(
    amount=100,
    phone="0712345678",
    idempotency_key=attempt.id,   # one key per payment attempt
)

if outcome.paid:
    print("Paid:", outcome.receipt)
else:
    print(outcome.message)        # already decoded, safe to show a customer
```
```java tab="java" title="The whole integration"
import dev.paylod.Paylod;
import dev.paylod.PaymentOutcome;

Paylod paylod = new Paylod(System.getenv("PAYLOD_API_KEY"));

// (phone, amount, accountReference, description, idempotencyKey)
PaymentOutcome outcome =
    paylod.collectAndWait("0712345678", 100, null, null, attempt.id());

if (outcome.isPaid()) {
    System.out.println("Paid: " + outcome.getReceipt());
} else {
    System.out.println(outcome.getMessage());
}
```
```kotlin tab="kotlin" title="The whole integration"
import dev.paylod.Paylod

val paylod = Paylod(System.getenv("PAYLOD_API_KEY"))

val outcome = paylod.collectAndWait(
    phone = "0712345678",
    amount = 100,                  // whole KES
    idempotencyKey = attempt.id,   // one key per payment attempt
)

if (outcome.paid) println("Paid: ${outcome.receipt}")
else println(outcome.message)      // already decoded, safe to show a customer
```

That is the full integration. Install `@paylod/node` and use one API key. paylod does the other work. If you do not use Node, make the same call over plain HTTP. See the [API reference](/docs/api).

## How it works

1. Create an **application** for a till or a paybill. Then put your Daraja consumer key, consumer secret, shortcode and passkey into the application. paylod encrypts these four values at rest, and the values never leave the server.
2. paylod gives you a **hosted callback URL**. Copy the callback URL into the Safaricom Daraja portal one time. That is the only Safaricom procedure that you do.
3. Your backend calls `collect()` with an amount and a phone number, and with a paylod **API key**. The customer's handset shows an M-Pesa PIN prompt.
4. M-Pesa sends the result to paylod's callback. paylod puts the result into a standard format, decodes the result code, and records the payment. Then paylod **sends you a signed webhook**, or you **poll** for the result. You select the method.

> Your integration must never let the client set the amount. Your server owns the amount. See [Secure integration](/docs/guides/security).

## Where to start

| If you want to… | Go to |
| --- | --- |
| Send your first STK push now | [Quickstart](/docs/quickstart) |
| Use the Node/TypeScript client | [Node SDK](/docs/sdk) |
| Send a payment from your terminal | [CLI](/docs/cli) |
| Understand keys, environments and idempotency | [Authentication](/docs/authentication) |
| Charge a customer correctly, in production | [Accept a payment](/docs/guides/accept-a-payment) |
| Know when the money is in your account | [Handle the result](/docs/guides/handle-the-result) |
| Find the parameters of an endpoint | [API reference](/docs/api) |
| Find what `1032` means | [Error codes](/docs/errors) |
| Let an AI agent do all of the above | [MCP server](/docs/mcp) |

## What you can build

- **Checkout** — collect a payment for an order. Fulfil the order when the webhook confirms the settled amount.
- **Payouts** — send money to customers, riders or staff (B2C). Reverse a transaction when you must refund a customer.
- **Offline payments** — register C2B, and counter payments to your till then reach your systems. Or give the customer a dynamic QR code.
- **Operations** — read a past transaction by receipt, or read the float of your shortcode.

Each item above is one SDK call, or one HTTP request. Each item uses one API key. You host no callback.
