Skip to content
NOSTREL
Quickstart

First payment in
about ten minutes.

Six steps. Each one says how long it takes and they add up to 10, so if you are 10 minutes in and it has not worked, something is wrong and you should tell us rather than keep going.

You will need a phone with M-Pesa on it, a terminal, and somewhere a webhook can reach. That is the entire prerequisite list.

  1. Step 01about 3 minutes

    Make an account and a key

    Sign up, then open Developers and create a key. It is shown once, so put it somewhere before you close the dialog. Start with a test key: it talks to Safaricom's sandbox and cannot move real money.

    Nothing to run here. This part happens in a browser, and it is the only part that does.

  2. Step 02about 1 minute

    Check the key works

    The balance endpoint is the cheapest possible proof that your key, your header and your base URL are all correct. If this returns JSON, everything after it is business logic rather than plumbing.

    is this thing onbash
    curl https://api.nostrel.com/v1/api/balance \  -H "Authorization: Bearer sk_test_••••••••" {  "available": 0,  "held": 0,  "currency": "KES"}
  3. Step 03about 2 minutes

    Somewhere to receive the answer

    The payment result arrives as a webhook, so you need a URL we can reach. For a first run a tunnel to your laptop is fine. Add the URL in the console and keep the signing secret it gives you.

    any tunnel will dobash
    # Whatever you already use. The point is a public HTTPS URL.npx localtunnel --port 4000# then paste https://<something>.loca.lt/hooks/nostrel into the console
  4. Step 04about 1 minute

    Raise a prompt

    Use your own number. A 202 means we have accepted it and are asking Safaricom to raise the prompt; it does not mean the payment happened, and treating it as though it does is the single most common first-integration mistake.

    the actual paymentbash
    curl https://api.nostrel.com/v1/api/collections \  -H "Authorization: Bearer sk_test_••••••••" \  -H "Idempotency-Key: my-first-payment" \  -d phone=254712345678 \  -d amount=10 \  -d reference=HELLO-1 {  "id": "col_7dj4a2zeialj",  "state": "initiated",  "amount": 10,  "currency": "KES",  "reference": "HELLO-1"}
  5. Step 05about 2 minutes

    Answer it

    This is the slow step and it is slow because a human is in it. The prompt appears on the handset, somebody reads it and types four digits. Nothing you write makes this part faster, which is worth knowing before you design a checkout around it.

    Nothing to run here either. Pick up the phone and answer the prompt, which is the one step no API removes.

  6. Step 06about 1 minute

    Read the webhook, and verify it

    Verify the signature over the raw bytes before you trust a single field. An unverified webhook endpoint is a public URL that credits orders, and people do find them.

    the smallest correct handlerts
    import { createHmac, timingSafeEqual } from 'node:crypto'; export function verify(secret, header, rawBody) {  const parts = Object.fromEntries(header.split(',').map((kv) => kv.split('=')));  const age = Math.abs(Date.now() / 1000 - Number(parts.t));  if (!parts.t || !parts.v1 || age > 300) return false;   const expected = createHmac('sha256', secret).update(parts.t + '.' + rawBody).digest('hex');  const a = Buffer.from(parts.v1, 'utf8');  const b = Buffer.from(expected, 'utf8');  return a.length === b.length && timingSafeEqual(a, b);}

If it did not work

The four things it usually is

In roughly this order of likelihood, from watching people do this.

  1. 01

    The prompt never appeared

    Check the number is in international form with no plus sign: 254712345678, not 0712345678 and not +254712345678. This is the most common one by a distance.

  2. 02

    202 but no webhook

    The 202 only says we accepted the request. Check the endpoint is saved in the console, that it is HTTPS, and that your tunnel is still up. Tunnels expire quietly.

  3. 03

    Signature never verifies

    You are almost certainly hashing a re-serialised object rather than the bytes that arrived. Most frameworks parse the body before your handler sees it. Capture the raw buffer first.

  4. 04

    401 on every call

    A test key on the live URL, or the other way around. The key prefix tells you which you are holding.

Then go and break it

A payment that works is the easy half. Before you ship, make it fail on purpose: cancel the prompt, enter the wrong PIN, let it time out, and drop a callback. Those four are what you will actually meet in production, and meeting them for the first time on a Friday afternoon is avoidable.

How to trigger each failure

Stuck on something on this page?

Tell us which step and what you saw. A quickstart that loses people at step four is our bug, not yours, and we would like to know which step it is.