# M-Pesa SDKs and libraries for paylod

Every official paylod client — Node, PHP (Laravel-ready), Python, Java and Kotlin — plus the WooCommerce plugin. All mirror the same surface: collect, collectAndWait, check, verifyWebhook, decodeError.

paylod is building an official client for every runtime that a Kenyan checkout tends to run on. They are all thin wrappers over the same HTTP API documented in the [API reference](/docs/api), and every one of them mirrors the same surface — `collect`, `collectAndWait`, `check`, `verifyWebhook`, `decodeError` — so the code below reads the same shape whichever language you pick. Use the language picker in the top-left to switch every sample on this page (and across the docs) at once.

Only the Node client is published today. For every other language the samples on this page are a preview of a client that is still in development — the HTTP API underneath them works right now, and the cURL tab shows it.

Not on this list? The API is plain HTTP with a bearer token — call it directly. The [quickstart](/docs/quickstart) and the [API reference](/docs/api) both show the raw cURL.

## Official SDKs

| Language | Status | Install | Repository |
| --- | --- | --- | --- |
| Node / TypeScript | Released | `npm install @paylod/node` | [github.com/mosesmrima/paylod-sdk](https://github.com/mosesmrima/paylod-sdk) |
| PHP (Laravel-ready) | In development | Not published yet — use the [HTTP API](/docs/api) | [github.com/mosesmrima/paylod-php](https://github.com/mosesmrima/paylod-php) |
| Python | In development | Not published yet — use the [HTTP API](/docs/api) | [github.com/mosesmrima/paylod-python](https://github.com/mosesmrima/paylod-python) |
| Java | In development | Not published yet — use the [HTTP API](/docs/api) | [github.com/mosesmrima/paylod-jvm](https://github.com/mosesmrima/paylod-jvm) |
| Kotlin | In development | Not published yet — use the [HTTP API](/docs/api) | [github.com/mosesmrima/paylod-jvm](https://github.com/mosesmrima/paylod-jvm) |

**Node is the only client published today.** PHP, Python, Java and Kotlin are written and in testing, but nothing is on Packagist, PyPI or Maven Central yet — there is no install command for them that will resolve. The samples below are a preview of the surface those clients will ship with. Until they land, call the HTTP API directly: it is a bearer token and a JSON body, and the cURL tab on every sample shows the exact request.

The Node client has the deepest reference — [Node SDK](/docs/sdk) — and the others are being built to track it method for method. Where a language has its own idiom, the SDK uses it: Python exposes `collect_and_wait`, `verify_webhook` and `decode_error`; the others keep the `collectAndWait` / `verifyWebhook` / `decodeError` spelling.

> [!note] The PHP client is framework-agnostic and ships a Laravel service provider and facade, so on Laravel you will be able to resolve `Paylod` from the container and read config from `config/paylod.php` instead of constructing it by hand.

Every SDK repository is public — read the source, file an issue, or open a pull request. Java and Kotlin share one repository, [paylod-jvm](https://github.com/mosesmrima/paylod-jvm). A public repository is not a release: the unreleased clients still have no install command that will resolve, and the repository is there to read, not to depend on.

## Install

```bash tab="node" title="npm"
npm install @paylod/node
```
```text tab="php" title="PHP — not released yet"
The PHP SDK is not on Packagist yet, so there is no composer command to run.
Call the HTTP API directly in the meantime — switch to the cURL tab.
```
```text tab="python" title="Python — not released yet"
The Python SDK is not on PyPI yet, so there is no pip command to run.
Call the HTTP API directly in the meantime — switch to the cURL tab.
```
```text tab="java" title="Java — not released yet"
The Java SDK is not on Maven Central yet, so there is no coordinate to depend on.
Call the HTTP API directly in the meantime — switch to the cURL tab.
```
```text tab="kotlin" title="Kotlin — not released yet"
The Kotlin SDK is not on Maven Central yet, so there is no coordinate to depend on.
Call the HTTP API directly in the meantime — switch to the cURL tab.
```
```bash tab="curl" title="No dependency"
# Nothing to install — any HTTP client works.
export PAYLOD_API_KEY=mp_test_YOUR_API_KEY
```

## Initialize the client

One argument: your API key. There is no base URL to configure and no OAuth token to refresh.

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

export const paylod = new Paylod(process.env.PAYLOD_API_KEY!);
```
```php tab="php" title="paylod.php"
use Paylod\Paylod;

$paylod = new Paylod(getenv('PAYLOD_API_KEY'));
```
```python tab="python" title="paylod.py"
import os
from paylod import Paylod

paylod = Paylod(os.environ["PAYLOD_API_KEY"])
```
```java tab="java" title="Paylod setup"
import dev.paylod.Paylod;

Paylod paylod = new Paylod(System.getenv("PAYLOD_API_KEY"));
```
```kotlin tab="kotlin" title="Paylod setup"
import dev.paylod.Paylod

val paylod = Paylod(System.getenv("PAYLOD_API_KEY"))
```
```bash tab="curl" title="Just an env var"
# The "client" is a bearer token on every request.
export PAYLOD_API_KEY=mp_test_YOUR_API_KEY
```

> [!warn] The API key can move money, so it is a server-side secret. Never ship it in a browser, a mobile app, or anything a payer can open. See [Secure integration](/docs/guides/security).

## Collect a payment

`collectAndWait` sends the STK Push and polls until the payment settles, then hands back a `PaymentOutcome`: a customer-facing `message` (already decoded) and a `retryable` flag.

```ts tab="node" title="collect.ts"
const outcome = await paylod.collectAndWait({
  amount: 100,
  phone: "0712345678",
  idempotencyKey: attempt.id,   // one key per payment attempt — a double-click cannot charge twice
});

if (outcome.paid) fulfil(outcome.receipt);
else console.log(outcome.message);
```
```php tab="php" title="collect.php"
$outcome = $paylod->collectAndWait([
    'amount' => 100,
    'phone' => '0712345678',
    'idempotencyKey' => $attempt->id,   // one key per payment attempt
]);

if ($outcome->paid) {
    fulfil($outcome->receipt);
} else {
    echo $outcome->message;
}
```
```python tab="python" title="collect.py"
outcome = paylod.collect_and_wait(
    amount=100,
    phone="0712345678",
    idempotency_key=attempt.id,   # one key per payment attempt
)

if outcome.paid:
    fulfil(outcome.receipt)
else:
    print(outcome.message)
```
```java tab="java" title="Collect.java"
PaymentOutcome outcome = paylod.collectAndWait(
    CollectParams.builder()
        .amount(100)
        .phone("0712345678")
        .idempotencyKey(attempt.id())   // one key per payment attempt
        .build());

if (outcome.isPaid()) {
    fulfil(outcome.getReceipt());
} else {
    System.out.println(outcome.getMessage());
}
```
```kotlin tab="kotlin" title="Collect.kt"
val outcome = paylod.collectAndWait(
    amount = 100,
    phone = "0712345678",
    idempotencyKey = attempt.id,   // one key per payment attempt
)

if (outcome.paid) fulfil(outcome.receipt)
else println(outcome.message)
```
```bash tab="curl" title="Raw HTTP"
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 /status/:id
```

## Check a payment

`check` reads a payment and decodes it into the same renderable `PaymentOutcome`. This is what a polling endpoint in your own app should return to its frontend.

```ts tab="node" title="check"
const outcome = await paylod.check(paymentId);
outcome.status;   // "pending" | "succeeded" | "cancelled" | "failed"
outcome.message;  // renderable, already decoded
```
```php tab="php" title="check"
$outcome = $paylod->check($paymentId);
$outcome->status;   // "pending" | "succeeded" | "cancelled" | "failed"
$outcome->message;  // renderable, already decoded
```
```python tab="python" title="check"
outcome = paylod.check(payment_id)
outcome.status   # "pending" | "succeeded" | "cancelled" | "failed"
outcome.message  # renderable, already decoded
```
```java tab="java" title="check"
PaymentOutcome outcome = paylod.check(paymentId);
outcome.getStatus();   // "pending" | "succeeded" | "cancelled" | "failed"
outcome.getMessage();  // renderable, already decoded
```
```kotlin tab="kotlin" title="check"
val outcome = paylod.check(paymentId)
outcome.status    // "pending" | "succeeded" | "cancelled" | "failed"
outcome.message   // renderable, already decoded
```
```bash tab="curl" title="GET /status/:id"
curl https://paylod.dev/functions/v1/status/e69e5c00-8a01-44ed-b003-48e2e86a7c9e \
  -H "Authorization: Bearer mp_live_YOUR_API_KEY"
# → 200 { "id": "...", "status": "success", "mpesaReceipt": "UG1F3A1U7J", ... }
```

## Verify a webhook

Every SDK verifies the `x-webhook-signature` header — HMAC-SHA256 over `t + "." + rawBody` — and returns a typed event. Pass the **raw** request bytes, not a re-serialised object. The scheme is documented under [Webhooks](/docs/api/webhooks).

```ts tab="node" title="verifyWebhook"
const event = paylod.verifyWebhook({
  payload: rawBody,
  signature: headers["x-webhook-signature"],
  secret: process.env.PAYLOD_WEBHOOK_SECRET,
});
```
```php tab="php" title="verifyWebhook"
$event = $paylod->verifyWebhook(
    payload: $rawBody,
    signature: $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'],
    secret: getenv('PAYLOD_WEBHOOK_SECRET'),
);
```
```python tab="python" title="verify_webhook"
event = paylod.verify_webhook(
    payload=raw_body,
    signature=request.headers["x-webhook-signature"],
    secret=os.environ["PAYLOD_WEBHOOK_SECRET"],
)
```
```java tab="java" title="verifyWebhook"
WebhookEvent event = paylod.verifyWebhook(
    rawBody,
    request.getHeader("x-webhook-signature"),
    System.getenv("PAYLOD_WEBHOOK_SECRET"));
```
```kotlin tab="kotlin" title="verifyWebhook"
val event = paylod.verifyWebhook(
    payload = rawBody,
    signature = request.header("x-webhook-signature"),
    secret = System.getenv("PAYLOD_WEBHOOK_SECRET"),
)
```

## Decode an error

Turn a raw M-Pesa result code into a decoded, renderable error — offline, no network call. You rarely need this directly (`check` and `collectAndWait` already return a decoded `message`); it is here for logs, dashboards and support tooling. The whole catalogue is browsable in the [error reference](/docs/errors).

```ts tab="node" title="decodeError"
const err = paylod.decodeError(1032);
// err.title, err.cause, err.fix, err.category, err.retryable, err.customerMessage
```
```php tab="php" title="decodeError"
$err = $paylod->decodeError(1032);
// $err->title, $err->cause, $err->fix, $err->category, $err->retryable, $err->customerMessage
```
```python tab="python" title="decode_error"
err = paylod.decode_error(1032)
# err.title, err.cause, err.fix, err.category, err.retryable, err.customer_message
```
```java tab="java" title="decodeError"
DecodedError err = paylod.decodeError(1032);
// err.getTitle(), err.getCause(), err.getFix(), err.getCategory(), err.isRetryable(), err.getCustomerMessage()
```
```kotlin tab="kotlin" title="decodeError"
val err = paylod.decodeError(1032)
// err.title, err.cause, err.fix, err.category, err.retryable, err.customerMessage
```

## Plugins

| Plugin | Platform | Status | Repository |
| --- | --- | --- | --- |
| paylod for WooCommerce | WordPress / WooCommerce | In development — not yet downloadable | [github.com/mosesmrima/paylod-woocommerce](https://github.com/mosesmrima/paylod-woocommerce) |

The WooCommerce plugin adds M-Pesa as a checkout method: install it, paste an `mp_live_…` key, and the STK Push, the hosted callback and the order-status update are all handled for you — no code. It is not released yet — the repository above is public to read, but the plugin is not downloadable and should not be installed on a live store. Until it ships, a WordPress store can call the [HTTP API](/docs/api) from a small snippet or from any server-side hook.

## Next

- The deepest per-method reference: [Node SDK](/docs/sdk).
- The HTTP endpoints underneath every SDK: [API reference](/docs/api).
- Prefer the terminal? The [CLI](/docs/cli) sends the same STK Push in one command.
- Every result code, decoded: [error reference](/docs/errors).
