Powered by
SafaricomM-PESA

PesaBridge

Unified Payment Gateway

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_....

MethodEndpointWhat it does
POST/stkpushSend an M-Pesa prompt to a customer's phone
POST/qrCreate a scan-to-pay QR code
GET/transactions/:idCheck a payment's status
POST/linksCreate a shareable payment link
GET/linksList 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

  1. 1Sign up and create an app under API keys. Choose where your money is settled.
  2. 2Copy your secret key (starts with sk_). It is shown only once.
  3. 3Save it on your server as PESABRIDGE_SECRET_KEY.
Never put your secret key in a web page, mobile app or GitHub. Use it only on your server. If it leaks, rotate it in the dashboard.

2. Take a payment

Call POST /stkpush from your server. The customer gets an M-Pesa prompt on their phone and enters their PIN.

pay.js
// 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 request

Errors come back as JSON with a readable error message.