M-Pesa Checkout API

Collect M-Pesa payments on any website. No plugin needed: one signup, one API call.

1. Create an account

Sign up at https://pay.vunaonline.co.ke/api/signup. You get a Merchant ID, an API key and a webhook secret.

2. Create a payment

curl -X POST https://pay.vunaonline.co.ke/api/v1/payments \ -H "Authorization: Bearer mk_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"amount": 250, "reference": "ORDER-1024", "phone": "0712345678"}'

If you pass phone, the customer gets the M-Pesa prompt immediately (server-to-server). Without a phone, send the customer to the returned checkout_url: a hosted page where they type their number.

Response:

{ "id": "e3f9c2a81b7d4f6a9c05", "status": "sent", // created | sent | paid | failed "amount": 250, "checkout_url": "https://pay.vunaonline.co.ke/pay/e3f9c2a81b7d4f6a9c05" }

3. Check status

curl https://pay.vunaonline.co.ke/api/v1/payments/e3f9c2a81b7d4f6a9c05 \ -H "Authorization: Bearer mk_YOUR_API_KEY"
{ "id": "e3f9c2a81b7d4f6a9c05", "status": "paid", "amount": 250, "receipt": "SLK7X2M9QP", "paid_at": "2026-10-16T14:03:11.000Z" }

4. Webhook (instant notification)

You never send anything. The moment a payment completes, our servers POST a notification to you at the webhook URL you saved in your dashboard. Your end only listens.

This is what WE send to YOUR webhook URL: POST https://your-site.com/your-webhook Content-Type: application/json X-MK-Signature: hex hmac-sha256 of the raw body, keyed with your webhook secret {"id":"e3f9c2a81b7d4f6a9c05","status":"paid","amount":250, "reference":"ORDER-1024","receipt":"SLK7X2M9QP","paid_at":"..."}

The webhook is only a heads-up: the customer's money has already settled to your Till or bank account before it arrives, and if your server misses it we retry up to 10 times. You can always confirm a payment yourself with the status endpoint or your dashboard statement.

Verify the signature (Node.js):

const crypto = require("crypto"); const sig = crypto.createHmac("sha256", process.env.WEBHOOK_SECRET) .update(rawBody).digest("hex"); if (sig === req.headers["x-mk-signature"]) { /* genuine */ }

PHP:

$sig = hash_hmac("sha256", file_get_contents("php://input"), $WEBHOOK_SECRET); if ($sig === $_SERVER["HTTP_X_MK_SIGNATURE"]) { /* genuine */ }

Errors

401 invalid_api_key bad or missing API key 400 amount_invalid amount must be a whole number of KES, 1 to 1,000,000 400 phone_invalid use 07XXXXXXXX or 2547XXXXXXXX 404 payment_not_found no payment with that id

Merchant dashboard

Log in here with your email and password to see transactions, set your webhook URL and test webhooks.

Notes

Payments settle directly to the M-Pesa Till or bank account you register in your dashboard — your money is never held by anyone. Test with small amounts first. Questions: lipaonlinepayment@gmail.com.