Skip to content

Daraja compared

Daraja gives you endpoints. Pay gives you a payments system.

Daraja is the right foundation for M-Pesa, and Pay runs on it. The difference is everything you would otherwise build around it.

Charge by M-Pesa STK push
curl https://pay.noria.co.ke/v1/charges \
  -H "Authorization: Bearer $NORIA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: invoice-1042" \
  -d '{
    "amount_minor": 250000,
    "currency": "KES",
    "channel": "mpesa",
    "payer_phone": "254712345678",
    "reference": "INV-1042",
    "description": "BillGrid invoice INV-1042"
  }'
const response = await fetch("https://pay.noria.co.ke/v1/charges", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.NORIA_API_KEY}`,
    "Content-Type": "application/json",
    "Idempotency-Key": "invoice-1042",
  },
  body: JSON.stringify({
    "amount_minor": 250000,
    "currency": "KES",
    "channel": "mpesa",
    "payer_phone": "254712345678",
    "reference": "INV-1042",
    "description": "BillGrid invoice INV-1042"
  }),
});
const result = await response.json();
import os
import requests

result = requests.post(
    "https://pay.noria.co.ke/v1/charges",
    headers={
        "Authorization": f"Bearer {os.environ['NORIA_API_KEY']}",
        "Idempotency-Key": "invoice-1042",
    },
    json={
        "amount_minor": 250000,
        "currency": "KES",
        "channel": "mpesa",
        "payer_phone": "254712345678",
        "reference": "INV-1042",
        "description": "BillGrid invoice INV-1042",
    },
    timeout=30,
).json()
<?php
$ch = curl_init('https://pay.noria.co.ke/v1/charges');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('NORIA_API_KEY'),
        'Content-Type: application/json',
        'Idempotency-Key: invoice-1042',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'amount_minor' => 250000,
        'currency' => 'KES',
        'channel' => 'mpesa',
        'payer_phone' => '254712345678',
        'reference' => 'INV-1042',
        'description' => 'BillGrid invoice INV-1042',
    ]),
]);
$result = json_decode(curl_exec($ch), true);
body, _ := json.Marshal(map[string]any{
	"amount_minor": 250000,
	"currency":     "KES",
	"channel":      "mpesa",
	"payer_phone":  "254712345678",
	"reference":    "INV-1042",
	"description":  "BillGrid invoice INV-1042",
})
req, _ := http.NewRequest("POST", "https://pay.noria.co.ke/v1/charges", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+os.Getenv("NORIA_API_KEY"))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", "invoice-1042")
res, err := http.DefaultClient.Do(req)
{
  "id": "0193a1d9-fcc3-7c3a-8d9d-838bd6273c4b",
  "type": "succeeded",
  "sequence": 3,
  "created_at": "2026-11-14T09:41:07Z",
  "data": {
    "transaction_id": "0193a1d9-e0a2-7b71-9f0e-2a6c5f1d8e40",
    "direction": "collection",
    "status": "succeeded",
    "provider": "daraja",
    "channel": "mpesa",
    "currency": "KES",
    "amount_minor": 250000,
    "settled_minor": 250000,
    "reference": "INV-1042",
    "provider_receipt": "TKE4X7Q2LM"
  }
}

One request sends the prompt. Pay confirms the result with Safaricom, then posts a signed webhook.

Tokens, passwords and certificates

On bare Daraja you request OAuth tokens, build a base64 password from shortcode, passkey and timestamp, and RSA-encrypt an initiator password with Safaricom's certificate for B2C and reversals. Pay does each of these for every request.

Callbacks you can believe

A Daraja callback can be late, duplicated, out of order or missing. Pay authenticates it, confirms each STK push result with Safaricom, settles once and polls for anything that never arrived.

Retries that do not double-charge

Daraja has no idempotency keys. Pay adds them to every money-moving request and resolves timeouts by asking Safaricom, never by sending again.

More than M-Pesa

Airtel Money and card collections, and payouts to Airtel Money and banks, use the same API, webhooks and ledger, through SasaPay, KCB Buni and Paystack.

Daraja compared questions

Something else? Ask us.

Is Noria Pay built on Daraja?

Yes, for M-Pesa through Safaricom. Pay calls Daraja with your credentials; it does not replace your relationship with Safaricom.

Do I still need a Daraja app?

Yes. You create the app and go live with Safaricom as usual, then add its credentials to Pay.

Can I move an existing Daraja integration?

Yes. Add your existing credentials to a project and switch your STK push and B2C calls to the Pay API. Payments still settle to the same shortcode.

Related

Ready to take payments properly?

Tell us what you are building and which providers you use. We set up your workspace and help you go live.