Accept M-Pesa payments
Customers pay by M-Pesa prompt, and the money is settled to your account. Base URL: https://pesabridge.obedtech.co.ke/api/public/v1. Amounts are whole KES.
All endpoints
Every request goes to https://pesabridge.obedtech.co.ke/api/public/v1 and (except the health check) needs your secret key as Authorization: Bearer sk_....
| Method | Endpoint | What it does |
|---|---|---|
| POST | /stkpush | Send an M-Pesa prompt to a customer's phone |
| POST | /qr | Create a scan-to-pay QR code |
| GET | /transactions/:id | Check a payment's status |
| POST | /links | Create a shareable payment link |
| GET | /links | List your payment links |
| GET | / | Health check and endpoint list (no key needed) |
You can also open https://pesabridge.obedtech.co.ke/api/public/v1 in a browser to see the live endpoint list and whether the API is up.
1. Get your key
- 1Sign up and create an app under API keys. Choose where your money is settled.
- 2Copy your secret key (starts with
sk_). It is shown only once. - 3Save it on your server as
PESABRIDGE_SECRET_KEY.
2. Take a payment
Call POST /stkpush from your server. The customer gets an M-Pesa prompt on their phone and enters their PIN.
// Node.js 18+ (keep this on your server, never in the browser)
const res = await fetch("https://pesabridge.obedtech.co.ke/api/public/v1/stkpush", {
method: "POST",
headers: {
Authorization: "Bearer " + process.env.PESABRIDGE_SECRET_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
amount: 100,
phone: "0712345678",
accountReference: "ORDER-1001",
}),
});
console.log(await res.json()); // { transactionId, status: "processing", ... }phone can be 07…, 2547… or +2547…. accountReference is your own order id (optional). Send an Idempotency-Key header to avoid charging twice on retries.
3. Check the result
GET https://pesabridge.obedtech.co.ke/api/public/v1/transactions/<transactionId>
Authorization: Bearer sk_live_...
{ "status": "success", "amount": 100, "mpesaReceipt": "SLK7YT2QX1" }status is processing, success, failed or cancelled. Treat only success as paid.
Webhooks
Instead of polling, set a webhook URL on your app. We POST to it when a payment is done, and retry if your server does not reply with a 2xx.
{
"event": "transaction.success",
"data": { "id": "...", "amount": 100, "accountReference": "ORDER-1001", "mpesaReceipt": "SLK7YT2QX1" }
}Every request has an X-Mpesa-Gateway-Signature header: an HMAC-SHA256 of the raw body using your app's signing secret. Check it before trusting the request.
QR & payment links
Scan to pay: POST /qr with { "amount": 100 } returns qrCodeDataUrl. Show it in an <img> when you do not have the customer's phone number. Results come by the same webhook and status endpoint.
Payment links: POST /links with an optional label and amount returns a url you can share on WhatsApp or SMS. This needs an approved plan.
Errors
401 Invalid API key
403 Key not approved yet
422 Invalid input (e.g. bad phone number)
429 Too many requests (limit: 60 per minute)
502 Safaricom rejected the requestErrors come back as JSON with a readable error message.
