Build payments once. Ship everywhere.
A single, predictable API for M-Pesa, mobile money, cards, bank transfers, stablecoins and payouts — with the reliability primitives you'd build yourself, already done.
Designed to be boring, in the best way.
The primitives that make payments reliable are part of the platform — not left as an exercise.
REST + JSON
Resource-oriented URLs, standard HTTP verbs and JSON bodies. Amounts are integers in minor units — no floating-point surprises.
Idempotency keys
Send an Idempotency-Key header with any POST. Retries return the original response, so a network blip never double-charges.
Signed webhooks
Every event is signed with HMAC-SHA256 in Imaripay-Signature, and retried with exponential backoff for about 45 hours.
Sandbox
Test and live modes are fully isolated with separate keys. Magic numbers let you simulate every outcome deterministically.
Reconciliation built in
Pending payments are continuously re-checked upstream; late confirmations are captured and delivered as normal events.
Consistent errors
Every error has an HTTP status, a stable machine-readable code and a human message you can show or log.
Your first payment in three steps.
Create an account to get sandbox keys instantly. Authenticate with your secret key as a Bearer token — never expose it in a browser or mobile app.
- 1
Create a payment
Send the amount in minor units, the currency, the method and the customer's phone. M-Pesa and mobile money payments trigger a PIN prompt on the customer's handset.
cURLcurl https://imaripay.com/api/v1/payments \ -H "Authorization: Bearer $IMARIPAY_KEY" \ -H "Idempotency-Key: order_1042" \ -d amount=150000 \ -d currency=KES \ -d method=mpesa \ -d phone=254712345678Node.jsconst res = await fetch("https://imaripay.com/api/v1/payments", { method: "POST", headers: { Authorization: `Bearer $${process.env.IMARIPAY_KEY}`, "Content-Type": "application/json", "Idempotency-Key": "order_1042", }, body: JSON.stringify({ amount: 150000, currency: "KES", method: "mpesa", phone: "254712345678", }), }); const payment = await res.json();Pythonimport os, requests r = requests.post( "https://imaripay.com/api/v1/payments", headers={ "Authorization": f"Bearer {os.environ['IMARIPAY_KEY']}", "Idempotency-Key": "order_1042", }, json={ "amount": 150000, "currency": "KES", "method": "mpesa", "phone": "254712345678", }, ) payment = r.json() - 2
Listen for webhooks
Payments complete asynchronously. Register an endpoint in the dashboard and we'll POST events such as payment.succeeded and payout.paid. Respond 2xx quickly — we retry anything else.
Event payload{ "id": "evt_0m4xa1c9…", "type": "payment.succeeded", "data": { "object": { "id": "pay_0m4x9kq2…", "status": "succeeded", … } } } - 3
Verify the signature
Each request carries a
Imaripay-Signature: t=…,v1=…header. Compute an HMAC-SHA256 of`${t}.${rawBody}`with your endpoint secret, compare in constant time, and reject stale timestamps.Node.jsimport { createHmac, timingSafeEqual } from "node:crypto"; export function verify(rawBody, header, secret) { const { t, v1 } = Object.fromEntries( header.split(",").map((p) => p.split("=")) ); if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false; const expected = createHmac("sha256", secret) .update(`$${t}.$${rawBody}`).digest("hex"); return v1?.length === expected.length && timingSafeEqual(Buffer.from(v1), Buffer.from(expected)); }Pythonimport hmac, hashlib, time def verify(raw_body: bytes, header: str, secret: str) -> bool: parts = dict(p.split("=", 1) for p in header.split(",")) t, v1 = parts.get("t", "0"), parts.get("v1", "") if abs(time.time() - int(t)) > 300: return False msg = f"{t}.".encode() + raw_body expected = hmac.new(secret.encode(), msg, hashlib.sha256).hexdigest() return hmac.compare_digest(v1, expected)
Simulate every outcome.
In test mode no real money moves. Use these magic values with your sk_test_ key to trigger specific results — and make sure your integration handles them gracefully.
| Test value | Method | Result |
|---|---|---|
| Phone ending 0000 M-Pesa / mobile money | M-Pesa / mobile money | Fails — insufficient funds |
| Phone ending 1111 M-Pesa / mobile money | M-Pesa / mobile money | Fails — customer cancelled |
| Phone ending 2222 M-Pesa / mobile money | M-Pesa / mobile money | Never completes, then expires |
| Any other phone M-Pesa / mobile money | M-Pesa / mobile money | Succeeds |
| “Decline” on the sandbox 3-D Secure page Card | Card | Declined |
| Any other card number Card | Card | Approved |
Payouts in test mode to a destination ending in 0000 fail with an invalid-account error.
The full API reference is one sign-up away.
Create a free account to access the complete reference — every endpoint, parameter, event type and error code — alongside your sandbox keys, webhook tester and request logs.