mpesa-daraja · git:20261027.a2d753d · 2026-10-27 · sha256 4b2d0a8cccb18b60

mpesa-daraja git:20261027.a2d753dA

Immutable. This exact content never changes and is served at /api/v1/blob/4b2d0a8cccb18b60.

---
name: mpesa-daraja
description: Entry point for Safaricom M-Pesa Daraja API work. Explains the async request/callback model, base URLs and the conventions every API shares, validates request bodies with a script, and routes to the specific daraja-* skill. Use whenever the user mentions M-Pesa, Mpesa, Daraja, Safaricom payments, Lipa na M-Pesa, Paybill, Till/Buy Goods, or Kenyan mobile-money integration.
---

# M-Pesa Daraja

Daraja is Safaricom's REST gateway to M-Pesa. Every API follows the same three steps:

1. **Get a token**: an OAuth client-credentials request returns a bearer token that is valid for about 1 hour → `daraja-auth`
2. **Send the request**: the synchronous reply `ResponseCode: "0"` means **accepted for processing**. It does **not** mean the payment succeeded.
3. **Receive the result asynchronously** on your CallBackURL or ResultURL → `daraja-callbacks`

**Rule #1: never mark money as moved based on the synchronous response.** Only the callback, `stk_query` or `transaction_status` can confirm it.

| Environment | Base URL |
|---|---|
| Sandbox | `https://sandbox.safaricom.co.ke` |
| Production | `https://api.safaricom.co.ke` |

Credentials, passkeys and certificates only work in the environment they were issued for. Mixing environments is the most common cause of errors.

## Route to the right skill

| Goal | Skill |
|---|---|
| Tokens, 401s, caching | `daraja-auth` |
| Charge a customer via a phone prompt (M-Pesa Express / STK Push) | `daraja-stk-push` |
| Receive Paybill/Till payments the customer starts; Pull missed transactions | `daraja-c2b` |
| Pay customers (refunds, salaries, Pochi la Biashara) | `daraja-b2c` |
| Pay businesses, top up a B2C account, B2B USSD checkout | `daraja-b2b` |
| Pay KRA taxes with a PRN | `daraja-tax-remittance` |
| Transaction Status, Reversal, Account Balance, reconciliation | `daraja-account-ops` |
| QR codes customers scan to pay | `daraja-dynamic-qr` |
| Recurring payments / standing orders | `daraja-ratiba` |
| SMS e-invoices with reminders and receipts | `daraja-bill-manager` |
| Webhook receivers | `daraja-callbacks` |
| Sandbox app, test credentials, tunnels, go-live | `daraja-sandbox-setup` |
| Secrets, SecurityCredential encryption | `daraja-credentials-security` |
| Error codes and "nothing happened" debugging | `daraja-troubleshooting` |
| Payment state machine, ledger tables, confirmations, refunds, reconciliation, go-live review | `daraja-payment-design` |

Building a whole use case rather than one API call? Start from a recipe:

| Use case | Recipe |
|---|---|
| Customers pay for orders in an app | `daraja-checkout` |
| Payroll, disbursements, refunds to phones | `daraja-payouts` |
| Customers pay a Paybill/Till; match to accounts | `daraja-paybill-collections` |
| Subscriptions and repeat charges | `daraja-recurring-billing` |
| An AI agent that sells and takes payment | `daraja-agent-storefront` |

New to Daraja, or want a guided tutorial in the sandbox? Use `daraja-teach-me`.

## Conventions shared by every API

- **Phone**: `2547XXXXXXXX` or `2541XXXXXXXX`: 12 digits, no `+`, no leading `0`. Bill Manager is the exception and uses `07…`.
- **Amount**: whole shillings only.
- **Timestamp**: `YYYYMMDDHHmmss` in **EAT (UTC+3)**.
- **Callback URLs**: public HTTPS with a valid certificate, no localhost or private IPs. Respond with 200 quickly.
- **Persist IDs before the callback can arrive**: `CheckoutRequestID`, `ConversationID`, `OriginatorConversationID`.
- **Idempotency**: callbacks can repeat, so key on the M-Pesa receipt and on your own request ID.
- **Daraja's own spellings are required**: `RecieverIdentifierType`, and `Occassion` in B2C v3/Pochi. Do not "fix" them.

## Validate before sending (feedback loop)

Run this against any request body you build, fix what it reports, and repeat until it prints `OK`:

```bash
python scripts/validate_request.py stk request.json
# apis: stk stk_query c2b_register c2b_simulate b2c b2pochi b2b tax status balance reversal qr ratiba
```

It checks required fields, phone/amount/timestamp formats, that the STK Password matches the Timestamp, HTTPS and reachable callbacks, CommandID values, field-length limits, and Daraja's spellings.

## Integration workflow

Copy this checklist and tick items off as you go:

```
- [ ] 1. Sandbox app + credentials in env/secret store (daraja-sandbox-setup, daraja-credentials-security)
- [ ] 2. Token helper with caching + 401 retry (daraja-auth)
- [ ] 3. Request builder for the chosen API; validate_request.py prints OK
- [ ] 4. Payments and events tables; persist request IDs before/at send time (daraja-payment-design)
- [ ] 5. Callback receiver: ack fast, store raw, process async, idempotent (daraja-callbacks)
- [ ] 6. Reconciliation: query anything PENDING too long (daraja-account-ops / stk query)
- [ ] 7. Test failure paths in sandbox (cancel, timeout, wrong PIN)
- [ ] 8. `check_integration.py` clean, then the go-live checklist (daraja-payment-design, daraja-sandbox-setup)
```

## MCP tools

If the `mpesa-daraja` server is connected (tool names start with `mpesa_`; some clients prefix them with the server name), call Daraja directly instead of writing throwaway scripts. For example:
- `mpesa_check_credentials` checks the configured keys.
- `mpesa_stk_push` and `mpesa_stk_query` run M-Pesa Express.
- `mpesa_parse_callback` parses a callback payload.
- `mpesa_explain_code` explains an error or result code.

Use `mpesa_stk_push_and_wait` for checkout and `mpesa_get_result` to read any final result. Money tools are idempotent per `idempotency_key`. Money-moving tools refuse to run in production unless the operator has explicitly enabled them.