Pay Lume Logo Pay Lume
key Get API Keys dashboard Dashboard
DEVELOPER API REFERENCE

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.

bolt

Sub-250ms Latency

Optimized direct connection to Safaricom Daraja API servers.

security

Atomic Token Security

Race-condition protected token reservation engine preventing duplicate charges.

hub

Custom Channel Routing

Route funds directly into your own Paybill or Till number seamlessly.

verified_user

HMAC Signed Webhooks

SHA-256 HMAC signature headers on every event delivery for maximum security.

Production API Endpoint
https://pay.lume.co.ke/api/v1
API STATUS Engine Active
{
  "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:

1

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

2

Maintain Service Token Balance

Ensure your account has available Service Tokens. Tokens are used by the gateway gas engine to process payment requests seamlessly.

3

Send HTTP Request

Make a POST request to /api/v1/stk/push with your bearer secret key in the authorization header.

Quickstart Request
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.

security

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.
Header Format Example
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:

https://pay.lume.co.ke/api/v1

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 Requests response.
429 Rate Limit Exceeded
{
  "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.
402 Token Protection Response
{
  "success": false,
  "code": "INSUFFICIENT_TOKENS",
  "message": "Insufficient service tokens. Payment cannot be processed at this time."
}
POST /api/v1/stk/push

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())
200 OK Initiation Response
{
  "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"
}
GET /api/v1/stk/query/{checkoutRequestId}

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).
Query Request Example
curl -X GET "https://pay.lume.co.ke/api/v1/stk/query/ws_CO_05102026130000192837" \
  -H "Authorization: Bearer sk_live_YOUR_SECRET_KEY"
200 OK Completed Response
{
  "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."
}

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.
Sample Webhook JSON Payload
{
  "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

  1. Extract the raw HTTP request body string.
  2. Compute hash_hmac('sha256', $rawBody, $webhookSecret).
  3. Compare with X-PayLume-Signature header value.
PHP HMAC Verification Code
$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 1Immediate
Attempt 21 minute
Attempt 35 minutes
Attempt 415 minutes
Attempt 51 hour
Attempt 66 hours (Final Attempt)
update

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.
Standard Error Schema
{
  "success": false,
  "code": "INVALID_PARAMETERS",
  "message": "phone_number, amount, and reference parameters are required.",
  "request_id": "req_8f1920a03b",
  "timestamp": 1791201642
}