Integrate with Kopokopo's M-Pesa payment APIs to build payment collections, disbursements, and settlements for Kenya. Use this skill for STK Push payment requests, receiving M-Pesa payments, sending payouts to customers, and transferring funds to bank accounts. Also trigger when user mentions 'Kopokopo', 'M-Pesa payments', 'STK Push', 'merchant payments', or needs payment integration for Kenya.
Kopokopo is Kenya's leading M-Pesa API platform enabling merchants to collect payments directly from customers' phones via STK Push, send payouts to M-Pesa wallets, and settle funds to bank accounts. It powers payment collections, merchant payouts, and business automation across Kenya with a production-grade REST API and webhooks.
Use Kopokopo when you need to:
Kopokopo's STK Push eliminates friction: customers see a payment prompt on their phone—no need to manually enter till numbers or navigate menus. This increases payment success rates and reduces cart abandonment.
Kopokopo uses OAuth 2.0 with Client Credentials flow for server-to-server authentication.
Exchange your Client ID and Client Secret for a Bearer token:
Sandbox:
POST https://sandbox.kopokopo.com/oauth/token
Content-Type: application/x-www-form-urlencoded
Production:
POST https://app.kopokopo.com/oauth/token
Content-Type: application/x-www-form-urlencoded
Request Body:
grant_type=client_credentials&client_id=YOUR_CLIENT_ID&client_secret=YOUR_CLIENT_SECRET
Response:
{
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "payment:write transfer:write"
}
Include the access token in the Authorization header for all subsequent requests:
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...
# Example: Store credentials securely
export KOPOKOPO_CLIENT_ID="your_client_id"
export KOPOKOPO_CLIENT_SECRET="your_client_secret"
export KOPOKOPO_BASE_URL="https://sandbox.kopokopo.com" # Sandbox
# export KOPOKOPO_BASE_URL="https://app.kopokopo.com" # Production
Obtain a Bearer token for all API requests.
Endpoint:
POST /oauth/token
Content-Type: application/x-www-form-urlencoded
Request:
grant_type=client_credentials&client_id=YOUR_CLIENT_ID&client_secret=YOUR_CLIENT_SECRET
Response (200 OK):
{
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "payment:write transfer:write"
}
Error Response (400):
{
"error": "invalid_client",
"error_description": "Client authentication failed"
}
Request payment from a customer via M-Pesa STK Push. The customer's phone displays a payment prompt—they enter their PIN to authorize.
Endpoint:
POST /api/v1/incoming_payments
Authorization: Bearer {access_token}
Content-Type: application/json
Request:
{
"phone_number": "+254712345678",
"first_name": "John",
"last_name": "Doe",
"email": "[email protected]",
"amount": 1500,
"currency": "KES",
"description": "Payment for Order #ORD-12345",
"merchant_reference": "ORD-12345",
"callback_url": "https://yourapp.com/webhooks/payment"
}
Field Descriptions:
phone_number: Customer's M-Pesa phone number (format: +254XXXXXXXXX)first_name, last_name: Customer details (optional but recommended)email: Customer email for receipt (optional)amount: Integer amount in KES (no decimals, minimum 100, maximum 150,000)currency: Always "KES" (Kenyan Shilling)description: Customer-facing payment descriptionmerchant_reference: Your unique transaction ID for tracking (max 100 chars)callback_url: (Optional) Webhook URL for payment notificationsResponse (201 Created):
{
"data": {
"id": "incoming_payment_8f9a7c3b2e1d4a5f",
"status": "PENDING",
"phone_number": "+254712345678",
"first_name": "John",
"last_name": "Doe",
"amount": 1500,
"currency": "KES",
"merchant_reference": "ORD-12345",
"description": "Payment for Order #ORD-12345",
"created_at": "2025-02-24T10:30:00Z",
"expires_at": "2025-02-24T10:31:00Z"
}
}
Timeline:
Error Responses:
| Status | Error | Description |
|---|---|---|
| 400 | INVALID_PHONE | Phone number format invalid (must be +254...) |
| 400 | INVALID_AMOUNT | Amount < 100 or > 150,000 KES |
| 401 | UNAUTHORIZED | Invalid or expired token |
| 403 | INSUFFICIENT_BALANCE | Your Kopokopo account has insufficient funds |
| 429 | RATE_LIMITED | Too many requests—implement exponential backoff |
Retrieve the current status of a payment request.
Endpoint:
GET /api/v1/incoming_payments/{id}
Authorization: Bearer {access_token}
Response (200 OK) - Payment Pending:
{
"data": {
"id": "incoming_payment_8f9a7c3b2e1d4a5f",
"status": "PENDING",
"phone_number": "+254712345678",
"amount": 1500,
"currency": "KES",
"merchant_reference": "ORD-12345",
"created_at": "2025-02-24T10:30:00Z",
"expires_at": "2025-02-24T10:31:00Z"
}
}
Response (200 OK) - Payment Completed:
{
"data": {
"id": "incoming_payment_8f9a7c3b2e1d4a5f",
"status": "COMPLETED",
"phone_number": "+254712345678",
"amount": 1500,
"currency": "KES",
"merchant_reference": "ORD-12345",
"mpesa_reference": "MU5C7H6YT2",
"mpesa_receipt_number": "MPF12A45G78H",
"payment_date": "2025-02-24T10:30:45Z",
"created_at": "2025-02-24T10:30:00Z"
}
}
Response (200 OK) - Payment Failed:
{
"data": {
"id": "incoming_payment_8f9a7c3b2e1d4a5f",
"status": "FAILED",
"phone_number": "+254712345678",
"amount": 1500,
"currency": "KES",
"merchant_reference": "ORD-12345",
"failure_reason": "User Denied Transaction",
"created_at": "2025-02-24T10:30:00Z"
}
}
Payment Statuses:
PENDING: Waiting for customer to authorizeCOMPLETED: Payment received and funds settledFAILED: Customer rejected or timeout (30 seconds elapsed)Send funds to a customer's M-Pesa wallet (referral bonuses, seller payouts, refunds).
Endpoint:
POST /api/v1/pay
Authorization: Bearer {access_token}
Content-Type: application/json
Request:
{
"phone_number": "+254712345678",
"amount": 1000,
"currency": "KES",
"reference": "PAYOUT-SELLER-001",
"description": "Seller payout for completed orders",
"callback_url": "https://yourapp.com/webhooks/payout"
}
Field Descriptions:
phone_number: Recipient's M-Pesa phone (format: +254XXXXXXXXX)amount: Integer amount in KES (minimum 100, maximum 150,000)currency: Always "KES"reference: Your unique payout ID for trackingdescription: Customer-facing payout description (shown in M-Pesa receipt)callback_url: (Optional) Webhook URL for payout notificationsResponse (201 Created):
{
"data": {
"id": "payout_b4f2a8e7d1c9g5h3",
"status": "PROCESSING",
"phone_number": "+254712345678",
"amount": 1000,
"currency": "KES",
"reference": "PAYOUT-SELLER-001",
"description": "Seller payout for completed orders",
"created_at": "2025-02-24T10:35:00Z"
}
}
Error Responses:
| Status | Error | Description |
|---|---|---|
| 400 | INVALID_PHONE | Phone number format invalid |
| 400 | INVALID_AMOUNT | Amount < 100 or > 150,000 KES |
| 401 | UNAUTHORIZED | Invalid or expired token |
| 403 | INSUFFICIENT_BALANCE | Your Kopokopo account has insufficient funds for payout |
| 429 | RATE_LIMITED | Too many requests |
Payout Timeline:
PROCESSING immediatelyCOMPLETED or FAILEDRetrieve the current status of a payout.
Endpoint:
GET /api/v1/pay/{id}
Authorization: Bearer {access_token}
Response (200 OK) - Payout Completed:
{
"data": {
"id": "payout_b4f2a8e7d1c9g5h3",
"status": "COMPLETED",
"phone_number": "+254712345678",
"amount": 1000,
"currency": "KES",
"reference": "PAYOUT-SELLER-001",
"mpesa_reference": "MU5C7H6YT2",
"mpesa_transaction_id": "MPF12A45G78H",
"completed_at": "2025-02-24T10:35:03Z",
"created_at": "2025-02-24T10:35:00Z"
}
}
Response (200 OK) - Payout Failed:
{
"data": {
"id": "payout_b4f2a8e7d1c9g5h3",
"status": "FAILED",
"phone_number": "+254712345678",
"amount": 1000,
"currency": "KES",
"reference": "PAYOUT-SELLER-001",
"failure_reason": "Invalid M-Pesa account",
"created_at": "2025-02-24T10:35:00Z"
}
}
Payout Statuses:
PROCESSING: Queued for transmissionCOMPLETED: Funds delivered to recipient's M-Pesa accountFAILED: Delivery failed (invalid number, M-Pesa errors, etc.)Retrieve a paginated list of all payment requests (STK Push transactions).
Endpoint:
GET /api/v1/incoming_payments?page=1&per_page=20&status=COMPLETED&from_date=2025-02-01&to_date=2025-02-28
Authorization: Bearer {access_token}
Query Parameters:
page: Page number (1-indexed, default: 1)per_page: Records per page (max 100, default: 20)status: Filter by status (PENDING, COMPLETED, FAILED)from_date: ISO 8601 date (YYYY-MM-DD)to_date: ISO 8601 date (YYYY-MM-DD)Response (200 OK):
{
"data": [
{
"id": "incoming_payment_8f9a7c3b2e1d4a5f",
"status": "COMPLETED",
"phone_number": "+254712345678",
"amount": 1500,
"currency": "KES",
"merchant_reference": "ORD-12345",
"mpesa_reference": "MU5C7H6YT2",
"mpesa_receipt_number": "MPF12A45G78H",
"payment_date": "2025-02-24T10:30:45Z",
"created_at": "2025-02-24T10:30:00Z"
},
{
"id": "incoming_payment_7e8c6b4a3f2d1e9h",
"status": "COMPLETED",
"phone_number": "+254798765432",
"amount": 2000,
"currency": "KES",
"merchant_reference": "ORD-12346",
"mpesa_reference": "MU5C7H6YT3",
"mpesa_receipt_number": "MPF12A45G78I",
"payment_date": "2025-02-24T11:15:20Z",
"created_at": "2025-02-24T11:14:00Z"
}
],
"pagination": {
"page": 1,
"per_page": 20,
"total": 147,
"total_pages": 8
}
}
Retrieve a paginated list of all payouts sent.
Endpoint:
GET /api/v1/pay?page=1&per_page=20&status=COMPLETED&from_date=2025-02-01&to_date=2025-02-28
Authorization: Bearer {access_token}
Query Parameters: (Same as incoming payments)
Response (200 OK):
{
"data": [
{
"id": "payout_b4f2a8e7d1c9g5h3",
"status": "COMPLETED",
"phone_number": "+254712345678",
"amount": 1000,
"currency": "KES",
"reference": "PAYOUT-SELLER-001",
"mpesa_reference": "MU5C7H6YT2",
"completed_at": "2025-02-24T10:35:03Z",
"created_at": "2025-02-24T10:35:00Z"
}
],
"pagination": {
"page": 1,
"per_page": 20,
"total": 52,
"total_pages": 3
}
}
Transfer funds from your Kopokopo wallet to a pre-registered bank settlement account.
Endpoint:
POST /api/v1/settlement_transfers
Authorization: Bearer {access_token}
Content-Type: application/json
Request:
{
"settlement_account_id": "settlement_acc_1a2b3c4d5e6f7g8h",
"amount": 50000,
"currency": "KES",
"reference": "SETTLE-BANK-FEB-24",
"description": "Monthly settlement to business account"
}
Field Descriptions:
settlement_account_id: ID of pre-verified bank account (created via dashboard)amount: Integer amount in KEScurrency: Always "KES"reference: Your unique settlement ID for trackingdescription: Internal description for accountingResponse (201 Created):
{
"data": {
"id": "settlement_transfer_9x8y7z6w5v4u3t2s",
"status": "PROCESSING",
"settlement_account_id": "settlement_acc_1a2b3c4d5e6f7g8h",
"amount": 50000,
"currency": "KES",
"reference": "SETTLE-BANK-FEB-24",
"created_at": "2025-02-24T14:00:00Z",
"settlement_fee": 50
}
}
Settlement Details:
Endpoint:
GET /api/v1/settlement_transfers/{id}
Authorization: Bearer {access_token}
Response (200 OK) - Settlement Completed:
{
"data": {
"id": "settlement_transfer_9x8y7z6w5v4u3t2s",
"status": "COMPLETED",
"settlement_account_id": "settlement_acc_1a2b3c4d5e6f7g8h",
"amount": 50000,
"currency": "KES",
"settlement_fee": 50,
"reference": "SETTLE-BANK-FEB-24",
"bank_transaction_id": "BANK-TX-2025-02-24-001",
"completed_at": "2025-02-26T10:30:00Z",
"created_at": "2025-02-24T14:00:00Z"
}
}
Kopokopo sends webhooks to your registered URLs whenever payment or payout events occur. This enables real-time updates for order fulfillment, accounting, and notifications.
Webhooks are registered in your Kopokopo dashboard under Settings > Webhooks. Provide:
Every webhook includes a signature for verification. Verify the webhook before processing:
Webhook Headers:
X-Kopokopo-Signature: sha256=abcdef123456789...
X-Kopokopo-Request-Id: webhook_req_1a2b3c4d5e6f7g8h
X-Kopokopo-Timestamp: 2025-02-24T10:30:00Z
Node.js Webhook Verification:
const crypto = require('crypto');
function verifyKopokokoWebhook(body, signature, webhookSecret) {
const hash = crypto
.createHmac('sha256', webhookSecret)
.update(JSON.stringify(body))
.digest('hex');
const expectedSignature = `sha256=${hash}`;
return crypto.timingSafeEqual(expectedSignature, signature);
}
// Express.js example
app.post('/webhooks/kopokopo', express.json(), (req, res) => {
const signature = req.headers['x-kopokopo-signature'];
const webhookSecret = process.env.KOPOKOPO_WEBHOOK_SECRET;
if (!verifyKopokokoWebhook(req.body, signature, webhookSecret)) {
return res.status(401).json({ error: 'Signature verification failed' });
}
// Process webhook
handleKopokokoEvent(req.body);
res.json({ status: 'received' });
});
Python Webhook Verification:
import hmac
import hashlib
import json
def verify_kopokopo_webhook(body, signature, webhook_secret):
hash_obj = hmac.new(
webhook_secret.encode(),
body.encode(),
hashlib.sha256
)
expected_signature = f"sha256={hash_obj.hexdigest()}"
return hmac.compare_digest(expected_signature, signature)
# Flask example
from flask import request
@app.route('/webhooks/kopokopo', methods=['POST'])
def handle_kopokopo_webhook():
signature = request.headers.get('X-Kopokopo-Signature')
webhook_secret = os.getenv('KOPOKOPO_WEBHOOK_SECRET')
if not verify_kopokopo_webhook(request.get_data(), signature, webhook_secret):
return {'error': 'Signature verification failed'}, 401
body = request.get_json()
handle_kopokopo_event(body)
return {'status': 'received'}, 200
Incoming Payment Completed:
{
"event": "incoming_payment.completed",
"request_id": "webhook_req_1a2b3c4d5e6f7g8h",
"timestamp": "2025-02-24T10:30:45Z",
"data": {
"id": "incoming_payment_8f9a7c3b2e1d4a5f",
"status": "COMPLETED",
"phone_number": "+254712345678",
"first_name": "John",
"last_name": "Doe",
"amount": 1500,
"currency": "KES",
"merchant_reference": "ORD-12345",
"mpesa_reference": "MU5C7H6YT2",
"mpesa_receipt_number": "MPF12A45G78H",
"payment_date": "2025-02-24T10:30:45Z",
"created_at": "2025-02-24T10:30:00Z"
}
}
Incoming Payment Failed:
{
"event": "incoming_payment.failed",
"request_id": "webhook_req_2b3c4d5e6f7g8h9i",
"timestamp": "2025-02-24T10:31:00Z",
"data": {
"id": "incoming_payment_8f9a7c3b2e1d4a5f",
"status": "FAILED",
"phone_number": "+254712345678",
"amount": 1500,
"currency": "KES",
"merchant_reference": "ORD-12345",
"failure_reason": "User Denied Transaction",
"created_at": "2025-02-24T10:30:00Z"
}
}
Payout Completed:
{
"event": "pay.completed",
"request_id": "webhook_req_3c4d5e6f7g8h9i0j",
"timestamp": "2025-02-24T10:35:03Z",
"data": {
"id": "payout_b4f2a8e7d1c9g5h3",
"status": "COMPLETED",
"phone_number": "+254712345678",
"amount": 1000,
"currency": "KES",
"reference": "PAYOUT-SELLER-001",
"mpesa_reference": "MU5C7H6YT2",
"mpesa_transaction_id": "MPF12A45G78H",
"completed_at": "2025-02-24T10:35:03Z",
"created_at": "2025-02-24T10:35:00Z"
}
}
Payout Failed:
{
"event": "pay.failed",
"request_id": "webhook_req_4d5e6f7g8h9i0j1k",
"timestamp": "2025-02-24T10:35:05Z",
"data": {
"id": "payout_b4f2a8e7d1c9g5h3",
"status": "FAILED",
"phone_number": "+254712345678",
"amount": 1000,
"currency": "KES",
"reference": "PAYOUT-SELLER-001",
"failure_reason": "Invalid M-Pesa account",
"created_at": "2025-02-24T10:35:00Z"
}
}
request_id as a deduplication key (Kopokopo may retry failed deliveries)Collect payments for online purchases with order confirmation and shipment integration.
Flow:
Code Example (Node.js):
const axios = require('axios');
async function createOrder(orderId, customerPhone, totalAmount) {
// Get access token
const tokenResponse = await axios.post(
'https://sandbox.kopokopo.com/oauth/token',
`grant_type=client_credentials&client_id=${process.env.KOPOKOPO_CLIENT_ID}&client_secret=${process.env.KOPOKOPO_CLIENT_SECRET}`,
{ headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }
);
const accessToken = tokenResponse.data.access_token;
// Send STK Push for payment
const paymentResponse = await axios.post(
'https://sandbox.kopokopo.com/api/v1/incoming_payments',
{
phone_number: customerPhone,
amount: totalAmount,
currency: 'KES',
merchant_reference: orderId,
description: `Payment for Order ${orderId}`,
callback_url: 'https://yourapp.com/webhooks/payment'
},
{ headers: { 'Authorization': `Bearer ${accessToken}` } }
);
// Save payment request ID for tracking
const paymentId = paymentResponse.data.data.id;
await db.updateOrder(orderId, { kopokopo_payment_id: paymentId, status: 'AWAITING_PAYMENT' });
return paymentId;
}
// Webhook handler
app.post('/webhooks/payment', express.json(), async (req, res) => {
const { data: payment } = req.body;
if (payment.status === 'COMPLETED') {
const orderId = payment.merchant_reference;
await db.updateOrder(orderId, { status: 'PAID', mpesa_reference: payment.mpesa_reference });
await triggerShipment(orderId);
} else if (payment.status === 'FAILED') {
const orderId = payment.merchant_reference;
await db.updateOrder(orderId, { status: 'PAYMENT_FAILED' });
}
res.json({ status: 'received' });
});
Collect buyer payments, deduct commission, and payout seller's share.
Flow:
Code Example (Python):
import requests
import os
from datetime import datetime
def process_marketplace_transaction(buyer_phone, seller_phone, order_amount):
# Get access token
token_response = requests.post(
'https://sandbox.kopokopo.com/oauth/token',
data={
'grant_type': 'client_credentials',
'client_id': os.getenv('KOPOKOPO_CLIENT_ID'),
'client_secret': os.getenv('KOPOKOPO_CLIENT_SECRET')
}
)
access_token = token_response.json()['access_token']
headers = {'Authorization': f'Bearer {access_token}'}
# Step 1: Collect payment from buyer
payment_response = requests.post(
'https://sandbox.kopokopo.com/api/v1/incoming_payments',
json={
'phone_number': buyer_phone,
'amount': order_amount,
'currency': 'KES',
'merchant_reference': f'ORDER-{datetime.now().timestamp()}',
'description': f'Payment for marketplace order'
},
headers=headers
)
payment_id = payment_response.json()['data']['id']
# Calculate seller payout (after 10% commission)
commission_rate = 0.10
commission = int(order_amount * commission_rate)
seller_payout = order_amount - commission
# Step 2: Send payout to seller (will be triggered by payment webhook)
payout_response = requests.post(
'https://sandbox.kopokopo.com/api/v1/pay',
json={
'phone_number': seller_phone,
'amount': seller_payout,
'currency': 'KES',
'reference': f'SELLER-PAYOUT-{payment_id}',
'description': f'Order proceeds after {commission_rate*100}% commission'
},
headers=headers
)
return {
'payment_id': payment_id,
'payout_id': payout_response.json()['data']['id'],
'order_amount': order_amount,
'commission': commission,
'seller_payout': seller_payout
}
Every payment and payout updates ledger and balance in real-time.
Flow:
Database Schema Example (SQL):
CREATE TABLE transactions (
id VARCHAR(64) PRIMARY KEY,
type ENUM('PAYMENT', 'PAYOUT', 'SETTLEMENT') NOT NULL,
status ENUM('PENDING', 'COMPLETED', 'FAILED') NOT NULL,
amount INT NOT NULL,
currency VARCHAR(3) NOT NULL,
phone_number VARCHAR(20),
merchant_reference VARCHAR(100),
mpesa_reference VARCHAR(50),
kopokopo_timestamp TIMESTAMP NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
webhook_request_id VARCHAR(64) UNIQUE, -- Deduplication key
INDEX idx_merchant_ref (merchant_reference),
INDEX idx_status (status),
INDEX idx_created_at (created_at)
);
CREATE TABLE account_balance (
account_id VARCHAR(64) PRIMARY KEY,
balance_ksh BIGINT NOT NULL,
last_transaction_id VARCHAR(64),
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
Webhook Handler (PHP):
<?php
function handleKopokokoWebhook($event_data, $request_id) {
// Idempotency check
$existing = db_query("SELECT id FROM transactions WHERE webhook_request_id = ?", [$request_id]);
if ($existing) {
return ['status' => 'already_processed'];
}
$payment = $event_data['data'];
$type = strpos($event_data['event'], 'incoming_payment') ? 'PAYMENT' : 'PAYOUT';
// Record transaction
db_insert('transactions', [
'id' => $payment['id'],
'type' => $type,
'status' => $payment['status'],
'amount' => $payment['amount'],
'currency' => $payment['currency'],
'phone_number' => $payment['phone_number'],
'merchant_reference' => $payment['merchant_reference'] ?? null,
'mpesa_reference' => $payment['mpesa_reference'] ?? null,
'kopokopo_timestamp' => $payment['payment_date'] ?? $payment['completed_at'],
'webhook_request_id' => $request_id
]);
// Update balance if completed
if ($payment['status'] === 'COMPLETED') {
$adjustment = ($type === 'PAYMENT') ? $payment['amount'] : -$payment['amount'];
db_query("UPDATE account_balance SET balance_ksh = balance_ksh + ? WHERE account_id = ?",
[$adjustment, 'primary_account']);
}
return ['status' => 'processed'];
}
?>
Collect recurring or one-time bill payments with automated reconciliation.
Flow:
Kopokopo returns structured error responses. Always handle errors gracefully.
| Status | Error Code | Meaning | Action |
|---|---|---|---|
| 400 | INVALID_PHONE | Phone number format invalid | Validate phone format (+254XXXXXXXXX) |
| 400 | INVALID_AMOUNT | Amount out of range (< 100 or > 150,000) | Adjust amount or inform user |
| 400 | INVALID_CURRENCY | Only KES supported | Use KES currency |
| 400 | MISSING_REQUIRED_FIELD | Required field missing in request | Check request payload |
| 401 | UNAUTHORIZED | Invalid or expired Bearer token | Request new token |
| 401 | INVALID_CLIENT | Client ID/Secret authentication failed | Verify credentials |
| 403 | INSUFFICIENT_BALANCE | Kopokopo account balance too low | Add funds to account |
| 403 | FORBIDDEN | Insufficient permissions for operation | Check account settings |
| 404 | NOT_FOUND | Payment/Payout ID not found | Verify transaction ID |
| 429 | RATE_LIMITED | Too many requests in short period | Implement exponential backoff |
| 500 | INTERNAL_ERROR | Server error | Retry with exponential backoff |
{
"status": "error",
"code": "INVALID_PHONE",
"message": "Phone number must be in format +254XXXXXXXXX",
"request_id": "req_1a2b3c4d5e6f7g8h"
}
Implement exponential backoff for transient failures (5xx, 429):
async function retryableRequest(method, url, data, maxRetries = 3) {
let attempt = 0;
while (attempt < maxRetries) {
try {
return await axios({ method, url, data });
} catch (error) {
if (error.response?.status >= 500 || error.response?.status === 429) {
const delayMs = Math.pow(2, attempt) * 1000 + Math.random() * 1000;
await new Promise(resolve => setTimeout(resolve, delayMs));
attempt++;
} else {
throw error; // Don't retry 4xx errors (except 429)
}
}
}
throw new Error(`Max retries exceeded after ${maxRetries} attempts`);
}
request_idX-RateLimit-* headers in responsesX-Kopokopo-Signaturerequest_id for idempotency