Reference
BitnovaPay API
REST API for Direct Pay (non-custodial STK orchestration) and Smart Gateway (custodial M-Pesa, cards, PayPal, crypto). JSON in, JSON out. HTTPS only.
Overview
The API has two namespaces - personal (individual user keys) and business (scoped to a business).
Bearer token: pk_live_-
https://pay.bitnova.co.ke/api/v1/
Bearer token: bsk_live_-
https://pay.bitnova.co.ke/api/v2/
Authentication
Pass your API key as a Bearer token in the Authorization header.
# Direct Pay - personal key curl -X GET https://pay.bitnova.co.ke/api/v1/balance \ -H "Authorization: Bearer pk_live_xxxxxxxxxxxx" # Smart Gateway - business key curl -X POST https://pay.bitnova.co.ke/api/v2/payment \ -H "Authorization: Bearer bsk_live_xxxxxxxxxxxx" \ -H "Content-Type: application/json" \ -d '{"method":"mpesa","amount":1000,"phone":"0712345678"}'
Quick Start
Direct Pay - Non-Custodial STK
Money goes straight to your paybill. Bitnova deducts a flat fee from your service wallet.
// 1. Create a channel (your paybill or till) curl -X POST https://pay.bitnova.co.ke/api/v1/channels \ -H "Authorization: Bearer pk_live_xxx" \ -H "Content-Type: application/json" \ -d '{"label":"Equity Bank","type":"paybill","shortcode":"247247"}' // 2. Fire STK push directly to that channel curl -X POST https://pay.bitnova.co.ke/api/v1/stk-push \ -H "Authorization: Bearer pk_live_xxx" \ -H "Content-Type: application/json" \ -d '{"channel_id":1,"amount":500,"phone":"0712345678"}'
Smart Gateway - Custodial M-Pesa
Funds collected by Bitnova, credited to your wallet after fees.
curl -X POST https://pay.bitnova.co.ke/api/v1/charges \
-H "Authorization: Bearer pk_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"method": "mpesa",
"amount": 1500,
"phone": "0712345678",
"description": "Order #1234"
}'Error Handling
All errors return a JSON body with an error field. HTTP status follows REST conventions.
{
"error": "Phone number required for M-Pesa.",
"code": 400
}| HTTP Status | Meaning |
|---|---|
400 | Bad request - missing or invalid parameter |
401 | Unauthorized - invalid or missing API key |
403 | Forbidden - insufficient permissions |
404 | Resource not found |
429 | Rate limit exceeded |
500 | Server error - try again |
Channels - Direct Pay
Channels are your own paybill, till, or bank shortcodes. STK pushes go directly to these destinations.
{ "data": [{ "id": 1, "label": "Equity Bank", "type": "paybill", "shortcode": "247247", "is_active": true }], "count": 1 }| Parameter | Type | Description | |
|---|---|---|---|
| label | string | required | e.g. "Equity Bank" |
| type | string | required | paybill | till | bank |
| shortcode | string | required | M-Pesa paybill or till number |
| account_reference | string | optional | Prefix on M-Pesa prompt |
STK Push - Direct Pay
| Parameter | Type | Description | |
|---|---|---|---|
| channel_id | integer | required | ID from GET /channels — this is the bank/till shortcode (PartyB) |
| amount | float | required | Amount in KES (min 1) |
| phone | string | required | 07XXXXXXXX or 2547XXXXXXXX — customer's M-Pesa (PartyA) |
| description | string | optional | Max 20 chars (shown on M-Pesa prompt) |
| payer_name | string | optional | Customer name for receipt |
| payer_email | string | optional | Send receipt to this address |
Response
{ "status":"pending","module":"A","reference":"MDA-XXXXXXXXXX","fee":5.00,"message":"STK sent to 0712345678. Complete on your phone." }Create Payment - Smart Gateway
Initiate a custodial payment. Funds collected by Bitnova, credited to your wallet after fees.
| Parameter | Type | Description | |
|---|---|---|---|
| method | string | required | mpesa | card | paypal | crypto |
| amount | float | required | Amount in KES |
| phone | string | if mpesa | 07XXXXXXXX or 2547XXXXXXXX |
| description | string | optional | Order description |
| payer_email | string | optional | Receipt email |
PayPal / Card response
{ "gateway":"paypal","redirect":"https://www.paypal.com/checkoutnow?token=xxx","reference":"BNP-XXXXXXXXXX" }Crypto response (NOWPayments)
{ "gateway":"nowpayments","redirect":"https://nowpayments.io/payment/?iid=xxxx","invoice_id":"xxxx" }Payment Status
{ "reference":"BNP-XXXXXXXXXX","status":"completed","amount":1500,"fee":15,"net_amount":1485,"method":"mpesa" }Transactions
| Query param | Type | Default | Description |
|---|---|---|---|
| status | string | all | pending | completed | failed |
| module | string | all | A (Direct Pay) | B (Smart Gateway) |
| method | string | all | mpesa | card | crypto |
| page | integer | 1 | Pagination |
| per_page | integer | 15 | Max 100 |
Wallet Balance
{ "balance":12500.00,"locked_balance":15.00,"available":12485.00,"currency":"KES" }locked_balance is reserved for pending Direct Pay service fees. Released automatically on completion or failure.Withdrawal
| Parameter | Type | Description | |
|---|---|---|---|
| amount | float | required | Min KES 100 |
| method | string | required | mpesa | bank | paypal |
| account | string | required | Phone, account number, or PayPal email |
Register Webhook
Webhooks can be registered via the API — useful for plugins and integrations that auto-configure on install. Registrations are idempotent: registering the same URL twice returns the existing secret.
| Parameter | Type | Description | |
|---|---|---|---|
| url | string | required | HTTPS URL to receive events |
| events | array | required | payment.success | payment.failed | withdrawal.success | withdrawal.failed |
| source | string | optional | api | woocommerce | shopify |
Response
{ "id": 1, "secret": "whsec_xxxxxxxxxxxxxxxx", "url": "https://yoursite.com/webhooks", "events": ["payment.success","payment.failed"] }secret securely — it is only returned once. Use it to verify incoming webhook signatures.{ "data": [{ "id": 1, "url": "https://...", "events": ["payment.success"], "is_active": true }], "count": 1 }{ "deleted": true, "id": 1 }Webhook Events
Configure endpoints in your dashboard under Settings → Webhooks. Bitnova sends signed JSON to your URL on every payment event.
Payload structure
{
"event": "payment.success",
"timestamp": 1735689600,
"data": {
"reference": "MDA-XXXXXXXXXX",
"module": "A",
"amount": 2500,
"fee": 5,
"method": "mpesa",
"status": "completed"
}
}Signature Verification
Every webhook includes an X-BitnovaPay-Signature header formatted as sha256=HMAC. Always verify before processing.
PHP
$payload = file_get_contents('php://input'); $sig = $_SERVER['HTTP_X_BITNOVAPAY_SIGNATURE']; $expected = 'sha256=' . hash_hmac('sha256', $payload, 'your_secret'); if (!hash_equals($expected, $sig)) { http_response_code(401); die('Unauthorized'); } $event = json_decode($payload, true);
Node.js
const sig = req.headers['x-bitnovapay-signature']; const expected = 'sha256=' + require('crypto') .createHmac('sha256', process.env.WEBHOOK_SECRET) .update(req.body, 'utf8').digest('hex'); if (!require('crypto').timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) return res.status(401).send('Unauthorized');