# CLI

Send a real M-Pesa STK Push from your terminal, decode any Daraja result code offline, and forward live webhooks to localhost without ngrok.

The `paylod` CLI sends a real M-Pesa STK Push from your terminal, decodes Daraja result codes offline, and forwards live webhooks to `localhost` without ngrok. It talks to the same API documented in the [API reference](/docs/api).

```bash title="Install"
npm install -g @paylod/cli
```

The package is `@paylod/cli`; the command it installs is `paylod`.

Or run it without installing:

```bash title="npx"
npx @paylod/cli errors 1032
```

Node 20 or newer.

## Set up

```bash title="Terminal"
paylod init
```

`init` signs you in (it opens your browser), lets you pick an application, and mints an API key for this machine. Credentials go into your OS keychain, or a `0600` file under `~/.config/paylod` if there is no keychain available.

If you only want to sign in:

```bash title="Terminal"
paylod login
paylod whoami       # who am I, and where is the token stored?
```

For CI, skip the login entirely and set `PAYLOD_API_KEY` in the environment. Every data-plane command (`collect`, `status`, `listen`) will use it.

## Take a payment

```bash title="Terminal"
paylod collect --phone 254712345678 --amount 100
```

The customer's phone rings. The CLI polls until the payment settles and prints the outcome — an M-Pesa receipt on success, or a decoded reason on failure.

| Flag | What it does |
| --- | --- |
| `-p, --phone <msisdn>` | Customer's number. `0712…`, `254712…`, `+254712…` all work. |
| `-a, --amount <kes>` | Whole shillings. M-Pesa rejects decimals. |
| `-r, --ref <reference>` | Your own correlation id (invoice/order number), returned on the result. ≤ 12 chars. Shown to the payer only on a Paybill; a Till never displays it. |
| `-d, --description <text>` | Payment description on the STK prompt. ≤ 64 chars. |
| `--idempotency-key <key>` | Reuse to retry safely without double-charging. |
| `--no-wait` | Fire the push and exit immediately, without polling. |
| `--timeout <secs>` | How long to wait for settlement. Default 120. |
| `--env <sandbox\|production>` | Which M-Pesa environment to charge on. |
| `--api-key <key>` | Use this key instead of `PAYLOD_API_KEY` or your stored profile. |

Look a payment up later, or watch it settle:

```bash title="Terminal"
paylod status e69e5c00-8a01-44ed-b003-48e2e86a7c9e
paylod status e69e5c00-8a01-44ed-b003-48e2e86a7c9e --watch
```

`status` decodes the failure reason for you — you never have to look `2001` up in a PDF.

## Decode a result code

Offline. No login, no network, no API key.

```bash title="Terminal"
paylod errors 1032          # why did that payment fail?
paylod errors --list        # the whole catalogue
paylod errors 2001 --json   # machine-readable, for your error handler
```

This is the fastest way to answer "what does `2001` mean" mid-incident. The same catalogue is browsable in the [error reference](/docs/errors).

> `4999` and `500.001.1001` decode as **pending**, not failed — they mean the customer has not entered their PIN yet. The payment is still live and still payable. Do not show a customer a failure on those codes.

## Webhooks on localhost, without ngrok

```bash title="Terminal"
paylod listen --forward http://localhost:3000/webhook
```

`listen` streams your application's live webhook events and re-POSTs each one to your local server, with the signature intact — so the code you are writing verifies a real signature against a real payload. No tunnel, no public URL, no ngrok.

| Flag | What it does |
| --- | --- |
| `-f, --forward <url>` | POST each event to this local URL. |
| `-e, --events <types...>` | Only forward these types (`payment.success`, `payment.failed`). |
| `--print-json` | Print the full JSON payload of every event. |
| `--secret <whsec>` | Signing secret. Otherwise taken from your profile, or fetched. |
| `--tolerance <secs>` | Signature freshness window. Default 300; `0` disables the check. |
| `--skip-verify` | Do not verify the HMAC. Debugging only. |
| `--port <port>` | Local receiver port. Default 4242. |

Roll (or fetch) the signing secret with:

```bash title="Terminal"
paylod webhooks secret
```

## Test without spending money

```bash title="Terminal"
paylod simulate --outcome user_cancelled
paylod simulate --interactive
```

`simulate` creates a payment in the paylod sandbox and forces an outcome — no Daraja call, no handset, no money. It is how you exercise your failure paths in CI.

| Outcome | Result code |
| --- | --- |
| `approve` | `0` |
| `wrong_pin` | `2001` |
| `insufficient_funds` | `1` |
| `user_cancelled` | `1032` |
| `timeout` | `1037` |

## Manage the integration

These commands do everything the dashboard does.

| Command | What it manages |
| --- | --- |
| `paylod apps list \| create \| use \| rename \| delete` | Applications — one per till or paybill. |
| `paylod creds` | Your Daraja consumer key, secret, shortcode and passkey. Write-only. |
| `paylod keys mint \| list \| revoke \| use` | paylod API keys (`mp_test_…` / `mp_live_…`). |
| `paylod webhooks list \| add \| toggle \| delete \| secret` | Webhook endpoints. |
| `paylod payments` | Browse your M-Pesa payments. |
| `paylod orgs` | Organizations. |

## Scripting

Every command takes a global `--json` flag and prints machine-readable output.

```bash title="Terminal"
paylod collect -p 254712345678 -a 100 --json | jq -r '.mpesaReceipt'
```

`paylod collect` exits `5` if the payment is still pending when the timeout expires — which is not a failure, just an unanswered prompt. A non-zero exit on a settled failure carries the decoded reason in the JSON.

## Environment variables

| Variable | What it does |
| --- | --- |
| `PAYLOD_API_KEY` | Merchant API key. Skips the config file entirely — this is how you run paylod in CI. |
| `PAYLOD_WEBHOOK_SECRET` | Signing secret for `paylod listen`. |
| `PAYLOD_API_BASE` | Override the API base. Defaults to `https://paylod.dev/functions/v1`. |
| `PAYLOD_CONFIG_DIR` | Where the CLI stores config. Default `~/.config/paylod`. |
| `PAYLOD_NO_KEYCHAIN=1` | Never use the OS keychain; use the `0600` file instead. Useful on headless Linux and in containers. |
| `NO_COLOR` | Disable colour. |

> [!warn] `mp_live_…` keys move real money. Keep them out of shell history and out of committed `.env` files.

## Next

- Building an app rather than a script? Use the [Node SDK](/docs/sdk).
- Wiring an AI agent? The [MCP server](/docs/mcp) exposes the same capabilities as tools.
- The HTTP endpoints underneath: [API reference](/docs/api).
