View as Markdown llms.txt

Quickstart#

No Safaricom account. No sandbox app. No tunnel. This runbook gets you from nothing to a settled payment with a receipt number.

What you need#

Node 20 or newer. That is the whole list.

bash
node --version

1. Start the server (10 seconds)#

bash
npx -y daraja-mcp

You should see:

[daraja-mcp] Simulator mode. No Safaricom credentials required.
[daraja-mcp] Mode: simulator
[daraja-mcp] Callback receiver listening on port 8787
[daraja-mcp] Ready.

The server is now speaking MCP on stdin and stdout. It started a local fake Daraja and a callback receiver, so the full asynchronous cycle works offline.

Leave it running, or press Ctrl-C and wire it into a client instead.

2. Wire it into your client (30 seconds)#

Claude Code

bash
claude mcp add daraja -- npx -y daraja-mcp

Claude Desktop

Edit claude_desktop_config.json:

json
{
  "mcpServers": {
    "daraja": {
      "command": "npx",
      "args": ["-y", "daraja-mcp"]
    }
  }
}

macOS puts that file at ~/Library/Application Support/Claude/claude_desktop_config.json. Restart the app afterwards.

Other clients: Install per client.

3. Take a payment (20 seconds)#

Ask your assistant:

Send an M-Pesa payment request for 100 shillings to 0712345678 and tell me whether it went through.

It should call stk_push_and_wait and come back with something like:

json
{
  "status": "success",
  "checkoutRequestId": "ws_CO_895779657909821",
  "resultCode": "0",
  "resultDesc": "The service request is processed successfully.",
  "metadata": {
    "Amount": 100,
    "MpesaReceiptNumber": "XZAWA9JEBX",
    "TransactionDate": 20260731121937,
    "PhoneNumber": 254712345678
  }
}

That is a complete payment cycle: prompt sent, customer accepted, callback received, receipt returned. On real Daraja the same call does the same thing, except a phone rings.

4. Try the failure paths#

This is the part you cannot do on real Daraja without a lot of setup and a cooperative human. The simulator keys scenarios off the amount:

Ask forYou get
100 shillingsSuccess with a receipt
1 shillingInsufficient funds
1032 shillingsCancelled by user
1037 shillingsTimeout, customer unreachable
2001 shillingsWrong PIN
9999 shillingsUpstream server error

Try:

Send an M-Pesa request for 1032 shillings to 0712345678.

You get a failure with ResultCode 1032 and no receipt. Note the response has no metadata at all: real Daraja omits CallbackMetadata entirely on failed payments, and code that reaches for the receipt number without checking is a common production crash. Better to meet it here.

5. Try a standing order#

Ratiba is the recurring-payment product: subscriptions, loan repayments, insurance premiums, SACCO contributions.

Set up a monthly M-Pesa standing order of 2000 shillings from 0712345678 for a gym membership, starting 1 August 2026 and ending 1 August 2027.

json
{
  "status": "success",
  "details": {
    "standingOrderName": "Gym membership",
    "amount": "2000.00",
    "reminderScheduleId": "3813734",
    "firstPaymentReminderDate": "20260807",
    "status": "ACTIVE",
    "Msisdn": "*********678"
  }
}

The masked MSISDN is not a bug: Safaricom masks it in Ratiba callbacks.

Standing order names must be unique per customer. Ask for the same name twice and the second attempt is rejected, same as on real Daraja.

What next#