Skip to content

Repository files navigation

@paylod/daraja

The Daraja (M-Pesa) SDK that turns ~800 lines of plumbing into ~8.

Tiny, fully typed, runtime-agnostic client for Safaricom's Daraja API. Automatic OAuth, decoded errors, and a callback parser that flattens M-Pesa's ugliest payload for you.

npm CI license types runtime


The 800 → 8 problem

Every M-Pesa integration re-implements the same boilerplate: base64 the credentials, hit the OAuth endpoint, cache the token, format an EAT timestamp, build the Password, get the BusinessShortCode vs ShortCode field names right, normalize phone numbers, and then decode a nested CallbackMetadata.Item[] array where 2001 cryptically means "wrong PIN".

That's hundreds of lines before you take a single shilling. This SDK is that, done once, tested, and typed:

import { Daraja } from "@paylod/daraja";

// Credentials come from the environment — never hard-code them.
const daraja = new Daraja({
  consumerKey: process.env.DARAJA_CONSUMER_KEY!,
  consumerSecret: process.env.DARAJA_CONSUMER_SECRET!,
  shortcode: process.env.DARAJA_SHORTCODE,
  passkey: process.env.DARAJA_PASSKEY,
  environment: "sandbox",
});

const res = await daraja.stkPush({
  amount: 10,
  phoneNumber: "2547...",       // 07.., +254.., or 254.. — all normalized for you
  accountReference: "INV-1",
  callbackUrl: "https://your.app/callback",
});

console.log(res.CheckoutRequestID); // 👈 you're taking payments

No manual token fetching. No timestamp math. No field-name landmines.

Install

npm i @paylod/daraja
# or: pnpm add @paylod/daraja · yarn add @paylod/daraja · bun add @paylod/daraja

Requires Node 18+ (or Deno / Bun / an edge runtime). Zero runtime dependencies.

Supported APIs

API Method Status
OAuth token (auto) (internal — never call it)
STK Push stkPush()
STK Query stkQuery()
Dynamic QR generateQr()
C2B — Register URL registerC2BUrls()
C2B — Simulate (sandbox) simulateC2B()
STK callback parser parseStkCallback()
Transaction Status buildTransactionStatusBody() (builder only) 🔜
Account Balance buildAccountBalanceBody() (builder only) 🔜
B2C · Reversal · B2B 🔜

The 🔜 initiator-credential APIs share an RSA SecurityCredential flow and an async ResultURL receiver. Their typed request builders already ship (src/initiator.ts); the live calls land in a future release. Follow along →

Parsing the callback

Daraja POSTs the real payment result to your callbackUrl as a deeply nested envelope. parseStkCallback flattens it into one clean, typed object — and still works on failure callbacks (which omit the metadata entirely):

import { parseStkCallback } from "@paylod/daraja";

app.post("/callback", (req, res) => {
  const result = parseStkCallback(req.body);

  if (result.success) {
    // result.amount, result.mpesaReceiptNumber, result.phoneNumber, result.transactionDate
    fulfilOrder(result.mpesaReceiptNumber, result.amount);
  } else {
    // Human-readable, categorized: result.decoded.title / .customerMessage / .retryable
    console.warn(result.decoded.title); // e.g. "Payment cancelled by the customer"
  }

  res.json({ ResultCode: 0, ResultDesc: "Accepted" }); // always 200 Daraja
});

Handling errors

Every failure is a DarajaError — inspect it, don't parse strings. Result codes are decoded into human + customer-facing messages:

import { Daraja, DarajaError, decodeResultCode } from "@paylod/daraja";

try {
  await daraja.stkPush({ /* ... */ });
} catch (err) {
  if (DarajaError.is(err)) {
    console.error(err.code, err.message, "retryable:", err.retryable);
    if (err.decoded) console.error(err.decoded.customerMessage);
  }
}

// Decode any result code directly:
decodeResultCode(2001).title;           // "Wrong M-Pesa PIN"
decodeResultCode(2001).category;        // "customer"  (NOT a credentials problem!)
decodeResultCode(1032).customerMessage; // "Payment cancelled — you can try again…"

Sandbox setup

  1. Create an app at developer.safaricom.co.ke and copy its Consumer Key + Consumer Secret.
  2. Use the shared sandbox shortcode 174379 and its Lipa na M-Pesa passkey (both on the Daraja portal's "Lipa Na M-Pesa Online" test credentials page).
  3. Expose a public HTTPS callback URL (e.g. an ngrok tunnel) — M-Pesa can't reach localhost.
  4. Trigger a push, then use simulateC2B() (sandbox only) to test C2B without a handset.

See examples/stk-push.ts for a runnable end-to-end script and .env.example for the variables it needs.

// Handy: test numbers Safaricom accepts in sandbox
phoneNumber: "254708374149"

Configuration

new Daraja({
  consumerKey: process.env.DARAJA_CONSUMER_KEY!,     // required
  consumerSecret: process.env.DARAJA_CONSUMER_SECRET!, // required
  environment: "sandbox",  // "sandbox" (default) | "production"
  shortcode: process.env.DARAJA_SHORTCODE,           // default shortcode (overridable per call)
  passkey: process.env.DARAJA_PASSKEY,               // required for STK
  timeoutMs: 30000,        // per-request timeout (default 30s)
  fetch: customFetch,      // inject your own fetch (proxy, retries, tests)
});

Keep credentials in the environment, not in source. Copy .env.example to .env, then load it however you like — Node 18+ has a built-in flag, no dependency needed:

node --env-file=.env your-script.js

Runtime support

Built on the global fetch and Web-standard APIs (TextEncoder, btoa, AbortController) — no Buffer, no node:* imports. Runs unchanged on Node 18+, Deno, Bun, Cloudflare Workers and other edge runtimes. Ships ESM + CJS + .d.ts.

Why this exists

We're paylod — a hosted M-Pesa "backend-as-a-service": no backend, no fees, no custody. We wrote this Daraja client for our own platform, tested it in production, and open-sourced the generic core because every Kenyan developer keeps rebuilding it.

This package is MIT licensed and deliberately independent of the paylod platform — no account, no lock-in. If you'd rather not host callbacks, reconcile missed webhooks, or manage credentials yourself, paylod.dev does that on top of exactly this code. Either way, take the SDK — it's yours. ⭐ the repo if it saved you a day.

Contributing

Issues and PRs welcome — see CONTRIBUTING.md and our Code of Conduct.

License

MIT © 2026 paylod / Moses Mrima

About

Open-source Node/TypeScript SDK for Safaricom Daraja 3.0 (M-Pesa) — the maintained one.

Topics

Resources

Code of conduct

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages