WHY NJIA
Mobile money shouldn’t cost you a sprint.
M-Pesa is how East Africa pays. Yet adding it to a product still takes days to weeks of work that has nothing to do with the product. Here is where that time goes, and how Njia gives it back.
Ask a developer in Nairobi how long their M-Pesa integration took and you’ll usually get a laugh before you get a number. The first STK push comes quickly. Everything after it is what hurts: callbacks that don’t arrive, callbacks that arrive twice, a sandbox that’s down the afternoon you need it, and a customer who swears they paid when your database says they didn’t.
We call this the integration tax: work every team pays before it can take its first shilling, and keeps paying after launch. Njia exists to remove it.
Where the time actually goes
Daraja, Safaricom’s API, gives you the rails. Everything you need to run on them safely is left to you.
1. Setup before the first request
Fetch an OAuth token and refresh it before it expires. Build the STK password from your shortcode, passkey and a timestamp in exactly the right format. Get the transaction type and callback URL right. None of it is hard, but all of it is fiddly, and the first mistake costs an afternoon.
2. Callbacks you can’t verify
The payment result reaches you as a callback to a URL you control. It carries no signature, so the usual defence is an allowlist of Safaricom’s IP addresses, and you hope that list stays current. Every endpoint that marks orders as paid is an endpoint someone can try to fake.
3. Callbacks that arrive once, twice, or never
If your server is down or slow when the callback arrives, you may simply never hear about that payment, and you have to poll for its status yourself. Duplicates happen too. A handler that isn’t built for both will, sooner or later, ship an order twice or not at all.
4. No safe way to retry
Your request to start a payment times out. Did the customer get the PIN prompt? Retry and you risk charging them twice. Don’t retry and you risk losing the sale.
5. A public URL just to develop
Callbacks need a public HTTPS address, so local development means a tunnel and re-registering the URL whenever it changes.
6. A sandbox you can’t steer
You can’t ask Daraja’s sandbox to make a customer cancel, time out, or run out of funds. So the unhappy paths, the ones that decide whether your support inbox stays quiet, usually get tested by real customers.
7. Then do it all again
Airtel Money has its own API, its own authentication and its own callback format. Add a second provider and you build a second integration.
What it looks like with Njia
One API key, one call, for any supported provider:
checkout.ts
import { Njia } from "@njia/sdk";
const njia = new Njia(process.env.NJIA_API_KEY);
const payment = await njia.payments.create(
{ channel: "mpesa", amount: 150000, phone: "254712345678", reference: "ORDER-42" },
{ idempotencyKey: "order-42" } // retry freely: same key, same payment
);
Switch "mpesa" to "airtel_money" and nothing
else changes. The outcome comes back as a webhook with the same
shape for every provider, signed so you can prove it came from us:
webhooks.ts
import { verifyWebhookSignature } from "@njia/sdk";
const ok = verifyWebhookSignature({
payload: rawBody, // the raw body, not parsed JSON
header: req.headers["njia-signature"], // t=…,v1=… (HMAC-SHA256)
secret: process.env.NJIA_WEBHOOK_SECRET,
});
Here is how each item above maps to what Njia does:
| The tax | With Njia |
|---|---|
| Tokens, passwords, timestamps | One secret key. We handle provider auth. |
| Unsigned callbacks | HMAC-signed webhooks with a timestamp, so forgeries and replays are rejected. |
| Missed or duplicate callbacks | Every event stored, failed deliveries retried, a log of each attempt, and one-click replay. |
| Unsafe retries | Idempotency keys: a retried request returns the original payment. |
| A tunnel for local dev | njia listen streams events straight to localhost. |
| A sandbox you can’t steer | Test numbers that cancel, time out, run out of funds, or deliver twice, every time. |
| One integration per provider | One API and one event format for M-Pesa and Airtel Money. |
Build from localhost
The Njia CLI holds a connection to our API and forwards every event to your machine as a signed webhook. No tunnel, no public URL, and your verification code runs exactly as it will in production.
$ npm install -g @njia/cli
$ njia listen --forward-to http://localhost:3000/webhooks
15:54:10 payment.succeeded pay_p3x60w… 1500.00 KES · 254700000000
↳ 200 3ms POST http://localhost:3000/webhooks
Test the unhappy paths on purpose
Our sandbox has six test numbers, each with fixed behaviour. Your error handling gets exercised before launch instead of discovered after it.
| Phone | What happens |
|---|---|
254700000000 | Succeeds after about 2 seconds |
254700000001 | Customer cancels the PIN prompt |
254700000002 | Times out after about 8 seconds |
254700000003 | Insufficient funds |
254700000004 | Succeeds slowly, after about 10 seconds |
254700000005 | Succeeds, and the webhook is delivered twice |
That last one matters most. If your handler marks an order paid twice when the same event arrives twice, you want to find out today, in test mode, not from a customer.
Your paybill. Your money.
Njia is an integration layer, not a wallet. When you go live you connect your own paybill or till, and money settles directly to your business as it does today. Your provider credentials are encrypted at rest. Pricing is a flat monthly fee plus a flat fee per payment, never a percentage of what you collect.
Who it’s for
- Startups that want to take payments this week and spend the saved time on the product.
- Agencies and freelancers who integrate mobile money for several clients. Separate workspaces keep each client’s keys, credentials and history apart, so you can hand a project over cleanly.
- Teams already running Daraja who are tired of debugging callbacks and want signed webhooks, retries and replay without building them.
Njia is Swahili for “the way”. We want to clear the way between your idea and your first payment.
Try it in five minutes.
The sandbox is free, with no paybill or credit card needed.
Get your free test key