Skip to content

Payouts

Pay people on M‑Pesa, Airtel Money or their bank

One payouts API across M-Pesa B2C, SasaPay and KCB Buni. Every payout carries an idempotency key, so a retry never sends money twice.

Send an M-Pesa payout
curl https://pay.noria.co.ke/v1/payouts \
  -H "Authorization: Bearer $NORIA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: payout-2207" \
  -d '{
    "amount_minor": 150000,
    "currency": "KES",
    "channel": "mpesa",
    "recipient_name": "Jay Kiharani",
    "recipient_phone": "254712345678",
    "reference": "PAYOUT-2207",
    "description": "SiteBooks site wages"
  }'
const response = await fetch("https://pay.noria.co.ke/v1/payouts", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.NORIA_API_KEY}`,
    "Content-Type": "application/json",
    "Idempotency-Key": "payout-2207",
  },
  body: JSON.stringify({
    "amount_minor": 150000,
    "currency": "KES",
    "channel": "mpesa",
    "recipient_name": "Jay Kiharani",
    "recipient_phone": "254712345678",
    "reference": "PAYOUT-2207",
    "description": "SiteBooks site wages"
  }),
});
const result = await response.json();
import os
import requests

result = requests.post(
    "https://pay.noria.co.ke/v1/payouts",
    headers={
        "Authorization": f"Bearer {os.environ['NORIA_API_KEY']}",
        "Idempotency-Key": "payout-2207",
    },
    json={
        "amount_minor": 150000,
        "currency": "KES",
        "channel": "mpesa",
        "recipient_name": "Jay Kiharani",
        "recipient_phone": "254712345678",
        "reference": "PAYOUT-2207",
        "description": "SiteBooks site wages",
    },
    timeout=30,
).json()
<?php
$ch = curl_init('https://pay.noria.co.ke/v1/payouts');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('NORIA_API_KEY'),
        'Content-Type: application/json',
        'Idempotency-Key: payout-2207',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'amount_minor' => 150000,
        'currency' => 'KES',
        'channel' => 'mpesa',
        'recipient_name' => 'Jay Kiharani',
        'recipient_phone' => '254712345678',
        'reference' => 'PAYOUT-2207',
        'description' => 'SiteBooks site wages',
    ]),
]);
$result = json_decode(curl_exec($ch), true);
body, _ := json.Marshal(map[string]any{
	"amount_minor":    150000,
	"currency":        "KES",
	"channel":         "mpesa",
	"recipient_name":  "Jay Kiharani",
	"recipient_phone": "254712345678",
	"reference":       "PAYOUT-2207",
	"description":     "SiteBooks site wages",
})
req, _ := http.NewRequest("POST", "https://pay.noria.co.ke/v1/payouts", 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", "payout-2207")
res, err := http.DefaultClient.Do(req)

The same idempotency key always returns the original payout, so a retry never sends money twice.

Every destination your payees use

Send to M-Pesa through Daraja B2C, SasaPay or KCB Buni; to Airtel Money and wallets through SasaPay; and to bank accounts through SasaPay or KCB Buni. Routing picks an enabled method that supports the channel and currency.

Safe to retry

Send the same idempotency key and you get the original payout back. Change the request under the same key and Pay refuses it. A timeout leaves the payout in an unknown state, and Pay never sends it again until the provider answers or your statement settles it.

Know where each payout is

Every payout moves through recorded states with an immutable event history. Your systems get a signed webhook at each step, and anyone on the team can check a payout's status with the provider from the dashboard.

Check balances before a payout run

SasaPay and Paystack balances are available from the dashboard and the API, so you can see what is there before you send money out.

Payouts questions

Something else? Ask us.

Which providers can send payouts?

M-Pesa Daraja (B2C), SasaPay (M-Pesa, Airtel Money, bank and wallet) and KCB Buni (M-Pesa and bank).

Does Noria hold the money I pay out?

No. Payouts are sent from your own provider account with your own credentials. Noria never holds funds.

What if a payout request times out?

Pay records the outcome as unknown and settles it when the provider answers, or from your statement. It never sends a second payout to cover a first one that may have gone through.

Guides

All guides

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.