Pay Lume REST API Engine
High-velocity M-Pesa payment infrastructure engineered for Kenyan developers, platforms, and e-commerce applications.
The Pay Lume API enables seamless integration of automated Safaricom M-Pesa STK Push prompts, payment links, custom Daraja Till/Paybill routing, and signed realtime webhooks using standardized JSON HTTP requests.
Sub-250ms Latency
Optimized direct connection to Safaricom Daraja API servers.
Atomic Token Security
Race-condition protected token reservation engine preventing duplicate charges.
Custom Channel Routing
Route funds directly into your own Paybill or Till number seamlessly.
HMAC Signed Webhooks
SHA-256 HMAC signature headers on every event delivery for maximum security.
https://pay.lume.co.ke/api/v1
{
"system": "Pay Lume REST API",
"version": "v1.0.0",
"status": "OPERATIONAL",
"latency_ms": 142,
"supported_channels": ["STK_PUSH", "TILL", "PAYBILL"]
}
3-Minute Integration Quickstart
Follow these 3 simple steps to start accepting M-Pesa payments in your application:
Generate API Credentials
Log in to your merchant dashboard, navigate to API Keys, and click Generate Key Pair to obtain your Secret Key (sk_live_...).
Maintain Service Token Balance
Ensure your account has available Service Tokens. Tokens are used by the gateway gas engine to process payment requests seamlessly.
Send HTTP Request
Make a POST request to /api/v1/stk/push with your bearer secret key in the authorization header.
curl -X POST "https://pay.lume.co.ke/api/v1/stk/push" \
-H "Authorization: Bearer sk_live_YOUR_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"phone_number": "0712345678",
"amount": 250,
"reference": "DEMO-001"
}'
Authentication & Security Keys
The Pay Lume API relies on Secret Key Bearer Token authentication. All requests must be performed over HTTPS with your Secret Key supplied in the HTTP Authorization header.
Key Security Best Practices
Never expose your Secret Key (sk_live_...) in client-side code, mobile applications, or public Git repositories. Keep keys stored securely in server environment variables.
Required Request Headers
| Header | Value | Description |
|---|---|---|
Authorization |
Bearer sk_live_... |
Secret key generated under API Keys dashboard. |
Content-Type |
application/json |
Required for all POST and PUT endpoints. |
Accept |
application/json |
Defines expected JSON response format. |
Authorization: Bearer sk_live_a1b2c3d4e5f67890abcdef123456
Content-Type: application/json
Accept: application/json
Base URL & Rate Limiting
The base URL for all API resources is structured as follows:
Rate Limiting Policy
To maintain high availability across all merchants, API endpoints are subject to rate limiting:
- STK Push Requests: Up to 100 requests per second per merchant account.
- Query Endpoints: Up to 200 requests per second per merchant account.
- Exceeding limits triggers an HTTP
429 Too Many Requestsresponse.
{
"success": false,
"code": "RATE_LIMIT_EXCEEDED",
"message": "Rate limit exceeded. Maximum 100 requests per second allowed."
}
Service Token Gas Fee Engine
Pay Lume uses a Service Token engine to power payment execution. Service Tokens are deducted automatically when STK Push prompts are initiated.
- Atomic Reservation: Tokens are reserved instantly before triggering Daraja network calls to prevent race conditions.
- Automatic Refund: If a Safaricom network error or credential failure occurs, reserved tokens are released back to your balance automatically.
- Zero-Token Protection: If available token balance is zero or below required tokens, the API returns HTTP
402 Payment Required.
{
"success": false,
"code": "INSUFFICIENT_TOKENS",
"message": "Insufficient service tokens. Payment cannot be processed at this time."
}
Initiate STK Push Request
Triggers a real-time M-Pesa STK Push prompt on the customer's phone asking them to enter their M-Pesa PIN.
Request JSON Body Parameters
| Field | Type | Required | Description |
|---|---|---|---|
phone_number |
string | Required | Safaricom M-Pesa phone number (e.g. 0712345678 or 254712345678). |
amount |
number | Required | Amount in KES (must be greater than 0). |
reference |
string | Required | Unique order/invoice reference (max 12 alphanumeric chars). |
description |
string | Optional | Short payment description (max 20 chars, default: API Payment). |
curl -X POST "https://pay.lume.co.ke/api/v1/stk/push" \
-H "Authorization: Bearer sk_live_YOUR_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"phone_number": "0712345678",
"amount": 500,
"reference": "ORD-9912",
"description": "Store Purchase"
}'
const fetch = require('node-fetch');
async function triggerStkPush() {
const response = await fetch('https://pay.lume.co.ke/api/v1/stk/push', {
method: 'POST',
headers: {
'Authorization': 'Bearer sk_live_YOUR_SECRET_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
phone_number: '0712345678',
amount: 500,
reference: 'ORD-9912',
description: 'Store Purchase'
})
});
const data = await response.json();
console.log(data);
}
triggerStkPush();
$ch = curl_init('https://pay.lume.co.ke/api/v1/stk/push');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer sk_live_YOUR_SECRET_KEY',
'Content-Type: application/json'
],
CURLOPT_POSTFIELDS => json_encode([
'phone_number' => '0712345678',
'amount' => 500,
'reference' => 'ORD-9912',
'description' => 'Store Purchase'
])
]);
$response = curl_exec($ch);
curl_close($ch);
$result = json_decode($response, true);
import requests
url = "https://pay.lume.co.ke/api/v1/stk/push"
headers = {
"Authorization": "Bearer sk_live_YOUR_SECRET_KEY",
"Content-Type": "application/json"
}
payload = {
"phone_number": "0712345678",
"amount": 500,
"reference": "ORD-9912",
"description": "Store Purchase"
}
response = requests.post(url, json=payload, headers=headers)
print(response.json())
{
"success": true,
"code": "STK_INITIATED",
"message": "STK Push initiated successfully.",
"transaction_id": "a1b2c3d4-e5f6-7890-1234-56789abcdef0",
"checkout_request_id": "ws_CO_05102026130000192837",
"status": "PENDING"
}
Query Transaction Status
Check real-time payment status using the checkout_request_id returned during initial push.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
checkoutRequestId |
string | Required | Safaricom checkout request ID (e.g. ws_CO_05102026130000192837). |
curl -X GET "https://pay.lume.co.ke/api/v1/stk/query/ws_CO_05102026130000192837" \
-H "Authorization: Bearer sk_live_YOUR_SECRET_KEY"
{
"success": true,
"transaction_id": "a1b2c3d4-e5f6-7890-1234-56789abcdef0",
"checkout_request_id": "ws_CO_05102026130000192837",
"amount": 500.00,
"currency": "KES",
"phone_number": "254712345678",
"reference": "ORD-9912",
"status": "SUCCESS",
"mpesa_receipt": "SJK1928472",
"result_code": "0",
"result_desc": "The service request has been processed successfully."
}
Query Payment Link Details
Retrieve payment details and payment status for generated shareable Payment Links by reference.
{
"success": true,
"payment_link": {
"reference": "PL-A819F2",
"title": "Software License",
"amount": 1500.00,
"status": "ACTIVE",
"total_payments_received": 3
}
}
Webhooks & Event Subscriptions
Pay Lume uses webhooks to notify your backend applications asynchronously when payment events occur. Configure webhook endpoints in your Webhooks Settings.
Event Types
| Event Name | Description |
|---|---|
payment.success |
Fired when an M-Pesa payment is successfully completed. |
payment.failed |
Fired when payment is cancelled, times out, or PIN entry fails. |
token.low_balance |
Fired when merchant Service Token balance falls below 10 tokens. |
{
"event": "payment.success",
"timestamp": 1791201600,
"data": {
"transaction_id": "a1b2c3d4-e5f6-7890-1234-56789abcdef0",
"checkout_request_id": "ws_CO_05102026130000192837",
"reference": "ORD-9912",
"amount": 500.00,
"mpesa_receipt": "SJK1928472",
"phone": "254712345678",
"status": "COMPLETED"
}
}
HMAC Signature Verification
Every webhook POST request includes an X-PayLume-Signature header. Use your endpoint's Secret Key (whsec_...) to compute a SHA-256 HMAC digest of the raw request payload to verify authenticity and prevent spoofing.
Verification Algorithm
- Extract the raw HTTP request body string.
- Compute
hash_hmac('sha256', $rawBody, $webhookSecret). - Compare with
X-PayLume-Signatureheader value.
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_PAYLUME_SIGNATURE'] ?? $_SERVER['HTTP_X_PAYVERA_SIGNATURE'] ?? '';
$secret = 'whsec_your_webhook_secret_here';
$expectedSignature = hash_hmac('sha256', $payload, $secret);
if (hash_equals($expectedSignature, $signature)) {
// Signature valid - Process Webhook
http_response_code(200);
} else {
// Invalid signature - Reject Request
http_response_code(401);
}
Webhook Retry Policy
Your webhook server must respond with an HTTP 200 OK status code within 5 seconds. If your server returns an error or times out, Pay Lume will retry event delivery according to the following schedule:
| Attempt | Backoff Delay |
|---|---|
| Attempt 1 | Immediate |
| Attempt 2 | 1 minute |
| Attempt 3 | 5 minutes |
| Attempt 4 | 15 minutes |
| Attempt 5 | 1 hour |
| Attempt 6 | 6 hours (Final Attempt) |
Idempotent Processing
Always design your webhook receivers to be idempotent using transaction_id or checkout_request_id so duplicate event deliveries do not cause duplicate order fulfillment.
Response & Error Codes Reference
Pay Lume uses standard HTTP status codes combined with explicit machine-readable error strings in response payloads:
| Error Code | HTTP Status | Description |
|---|---|---|
INVALID_PARAMETERS |
400 | Required body field missing or invalid phone number / amount format. |
UNAUTHORIZED |
401 | Invalid or revoked API secret key supplied. |
INSUFFICIENT_TOKENS |
402 | Account Service Token balance insufficient. |
RATE_LIMIT_EXCEEDED |
429 | Request threshold exceeded (100 req/sec limit). |
DARAJA_ERROR |
500 | Safaricom M-Pesa gateway error or network timeout. |
{
"success": false,
"code": "INVALID_PARAMETERS",
"message": "phone_number, amount, and reference parameters are required.",
"request_id": "req_8f1920a03b",
"timestamp": 1791201642
}