SparkPesa API Reference
Accept M-Pesa payments (Kenya) and Mobile Money (Uganda) in your application with a single integration.
Getting Started
Base URL
https://www.sparkpesa.io/api/v1Quick Start
- 1Sign up at sparkpesa.io and create an organization
- 2Create a KES wallet (M-Pesa) or UGX wallet (Uganda Mobile Money)
- 3Generate an API key from Settings → API Keys
- 4Save your API Key and API Secret — the secret is shown only once
- 5Note your Wallet ID (UUID) or Wallet Code (8-digit number)
Supported Currencies & Providers
| Currency | Country | Provider | Operations |
|---|---|---|---|
KES | Kenya | M-Pesa | STK Push, B2C, Buy Goods, PayBill |
UGX | Uganda | MTN / Airtel | Request Payment, Send Payment |
Authentication
Every API request requires three headers:
| Header | Description |
|---|---|
| Authorization | Bearer <your_api_key> |
| X-Signature | HMAC-SHA256 signature of the request |
| X-Timestamp | Unix timestamp in seconds |
Generating the Signature
The signature is an HMAC-SHA256 hash of a concatenated string using your API Secret as the key.
{apiKey}{timestamp}{walletCode}{currency}{phoneNumber}{amount}{accountReference}Omit any field not in the request (use empty string). Field order is fixed.
const crypto = require('crypto');
const apiKey = 'sk_live_abc123def456...';
const apiSecret = 'ss_live_xyz789...';
const timestamp = Math.floor(Date.now() / 1000);
const payload = {
walletCode: '12345678',
currency: 'KES',
phoneNumber: '254712345678',
amount: 100,
accountReference: 'ORDER-001',
};
// Build signature string
const signatureString = `${apiKey}${timestamp}${payload.walletCode}${payload.currency}${payload.phoneNumber}${payload.amount}${payload.accountReference}`;
// Generate HMAC-SHA256 signature
const signature = crypto
.createHmac('sha256', apiSecret)
.update(signatureString)
.digest('hex');
const response = await fetch('https://www.sparkpesa.io/api/v1/payments/request-payment', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`,
'X-Signature': signature,
'X-Timestamp': timestamp.toString(),
},
body: JSON.stringify(payload),
});Timestamp Validation
Requests rejected if timestamp differs from server time by more than 5 minutes.
Rate Limiting
Default 100 req/min per API key. Custom limits configurable.
Wallet Identification
All endpoints accept either a wallet UUID or an 8-digit wallet code:
Option A: Wallet UUID
{ "walletId": "550e8400-e29b-41d4-a716-446655440000" }Option B: Wallet Code (human-friendly)
{ "walletCode": "12345678" }At least one must be provided. If both are provided, walletId takes precedence.
Wallet Management
Create, list, update, and delete wallets programmatically via the API. Wallet management endpoints use a simplified HMAC signature.
{apiKey}{timestamp}Unlike payment endpoints, wallet management uses only apiKey + timestamp for the signature — no body fields required.
Read Permissions
GET endpoints require wallets:view or wallet:balance
Write Permissions
POST, PATCH, DELETE require wallets:manage
List Wallets
/v1/walletsReturns all wallets belonging to the authenticated organization.
curl -X GET https://www.sparkpesa.io/api/v1/wallets \
-H "Authorization: Bearer $API_KEY" \
-H "X-Signature: $SIGNATURE" \
-H "X-Timestamp: $TIMESTAMP"Create Wallet
/v1/walletsCreate a new wallet. An 8-digit wallet code is auto-generated.
| Field | Type | Required | Description |
|---|---|---|---|
| name | string | Required | Wallet name (1–255 characters) |
| currency | string | Required | 3-letter ISO currency code (e.g. KES, UGX, USD) |
| description | string | Optional | Wallet description (max 500 characters) |
| walletType | string | Optional | "business" (default), "personal", or "escrow" |
{
"name": "Uganda Operations",
"currency": "UGX",
"description": "Wallet for Uganda mobile money",
"walletType": "business"
}Get Wallet Details
/v1/wallets/:walletIdReturns detailed wallet information including the 5 most recent transactions.
curl -X GET https://www.sparkpesa.io/api/v1/wallets/550e8400-e29b-41d4-a716-446655440000 \
-H "Authorization: Bearer $API_KEY" \
-H "X-Signature: $SIGNATURE" \
-H "X-Timestamp: $TIMESTAMP"Update Wallet
/v1/wallets/:walletIdUpdate a wallet's name, description, or webhook URL. At least one field must be provided.
| Field | Type | Required | Description |
|---|---|---|---|
| name | string | Optional | New wallet name (1–255 characters) |
| description | string | Optional | New description (max 500 characters) |
| webhookUrl | string | Optional | New webhook URL (or null to remove) |
{
"name": "Renamed Wallet",
"description": "Updated description",
"webhookUrl": "https://yourapp.com/webhooks/new"
}Delete Wallet
/v1/wallets/:walletIdDeactivate (soft-delete) a wallet. The wallet must have a zero balance and must not be the default wallet.
curl -X DELETE https://www.sparkpesa.io/api/v1/wallets/550e8400-e29b-41d4-a716-446655440000 \
-H "Authorization: Bearer $API_KEY" \
-H "X-Signature: $SIGNATURE" \
-H "X-Timestamp: $TIMESTAMP"400 if the wallet is the default wallet or has a remaining balance/pending transactions.Wallet Transfer
Transfer funds between two wallets in the same organization. Both wallets must use the same currency. Transfers are atomic — either both wallets update or neither does.
/v1/wallets/transfer{apiKey}{timestamp}| Field | Type | Required | Description |
|---|---|---|---|
| sourceWalletId | string (UUID) | Required | Wallet to debit funds from |
| destinationWalletId | string (UUID) | Required | Wallet to credit funds to |
| amount | number | Required | Amount to transfer (must be > 0) |
| description | string | Optional | Transfer description (max 255 chars) |
| reference | string | Optional | Custom reference (auto-generated if omitted) |
{
"sourceWalletId": "550e8400-e29b-41d4-a716-446655440000",
"destinationWalletId": "660f9511-f30c-52e5-b827-557766551111",
"amount": 5000,
"description": "Funds reallocation to Uganda ops",
"reference": "TRF-REALLOC-001"
}Same Currency
Both wallets must use the same currency (e.g. KES → KES).
Atomic
Debit and credit happen in a single database transaction — no partial transfers.
Instant
Transfers complete immediately with status completed.
Wallet Transactions
Retrieve a paginated list of transactions for a specific wallet. Supports filtering by status and type.
/v1/wallets/:walletId/transactionsQuery Parameters
| Field | Type | Required | Description |
|---|---|---|---|
| page | number | Optional | Page number (default: 1) |
| limit | number | Optional | Items per page, 1–100 (default: 20) |
| status | string | Optional | Filter: pending, processing, completed, failed, cancelled |
| type | string | Optional | Filter: payment_in, payment_out, transfer, fee, refund |
curl -X GET "https://www.sparkpesa.io/api/v1/wallets/550e8400-e29b-.../transactions?page=1&limit=10&status=completed" \
-H "Authorization: Bearer $API_KEY" \
-H "X-Signature: $SIGNATURE" \
-H "X-Timestamp: $TIMESTAMP"Ordering
Transactions are returned newest first (createdAt DESC).
Combining Filters
Use both status and type together, e.g. ?status=completed&type=transfer.
Request Payment
Initiate an STK Push to collect payment from a customer's M-Pesa account.
/v1/payments/request-payment| Field | Type | Required | Description |
|---|---|---|---|
| walletId | string | Either * | Wallet UUID |
| walletCode | string | Either * | 8-digit wallet code |
| phoneNumber | string | Required | Customer phone 254XXXXXXXXX |
| amount | number | Required | Amount in KES (10 – 250,000) |
| currency | string | Required | Must be "KES" |
| accountReference | string | Required | Your order/invoice reference |
| transactionDesc | string | Optional | Description shown to customer |
| callbackUrl | string | Optional | Webhook URL for notification |
| metadata | object | Optional | Custom key-value pairs |
{
"walletCode": "12345678",
"phoneNumber": "254712345678",
"amount": 500,
"currency": "KES",
"accountReference": "INV-2026-001",
"transactionDesc": "Payment for Invoice #001",
"callbackUrl": "https://yourapp.com/webhooks/sparkpesa"
}callbackUrl.Send Payment
Send money from your wallet to a customer's M-Pesa account.
/v1/payments/send-payment| Field | Type | Required | Description |
|---|---|---|---|
| walletId | string | Either * | Wallet UUID |
| walletCode | string | Either * | 8-digit wallet code |
| phoneNumber | string | Required | Recipient phone 254XXXXXXXXX |
| amount | number | Required | Amount in KES (10 – 250,000) |
| currency | string | Required | Must be "KES" |
| commandId | string | Optional | SalaryPayment, BusinessPayment, or PromotionPayment |
| description | string | Optional | Payment description |
| remarks | string | Optional | M-Pesa remarks |
| callbackUrl | string | Optional | Webhook URL for notification |
| metadata | object | Optional | Custom key-value pairs |
{
"walletCode": "12345678",
"phoneNumber": "254712345678",
"amount": 5000,
"currency": "KES",
"commandId": "SalaryPayment",
"description": "March 2026 salary",
"callbackUrl": "https://yourapp.com/webhooks/sparkpesa"
}Sufficient wallet balance (amount + estimated fee) is required. Funds are reserved immediately.
Buy Goods
Pay to an M-Pesa Till Number (Buy Goods).
/v1/payments/buy-goods| Field | Type | Required | Description |
|---|---|---|---|
| walletId | string | Either * | Wallet UUID |
| walletCode | string | Either * | 8-digit wallet code |
| tillNumber | string | Required | 5–7 digit till number |
| amount | number | Required | Amount in KES (1 – 999,999) |
| currency | string | Required | Must be "KES" |
| accountReference | string | Required | Payment reference |
| requesterPhone | string | Required | Requester phone 254XXXXXXXXX |
| remarks | string | Optional | Payment remarks |
| callbackUrl | string | Optional | Webhook URL |
{
"walletCode": "12345678",
"tillNumber": "518518",
"amount": 2500,
"currency": "KES",
"accountReference": "PO-2026-042",
"requesterPhone": "254712345678",
"remarks": "Office supplies"
}PayBill
Pay to an M-Pesa PayBill Number with an account number.
/v1/payments/paybill| Field | Type | Required | Description |
|---|---|---|---|
| walletId | string | Either * | Wallet UUID |
| walletCode | string | Either * | 8-digit wallet code |
| paybillNumber | string | Required | 5–7 digit paybill number |
| accountNumber | string | Required | Account/reference number |
| amount | number | Required | Amount in KES (1 – 999,999) |
| currency | string | Required | Must be "KES" |
| requesterPhone | string | Required | Requester phone 254XXXXXXXXX |
| remarks | string | Optional | Payment remarks |
| callbackUrl | string | Optional | Webhook URL |
{
"walletCode": "12345678",
"paybillNumber": "320320",
"accountNumber": "12345678901",
"amount": 3500,
"currency": "KES",
"requesterPhone": "254712345678",
"remarks": "KPLC electricity"
}Request Payment
Collect payment from a customer's mobile money account in Uganda (MTN / Airtel).
/v1/payments/request-payment-uganda| Field | Type | Required | Description |
|---|---|---|---|
| walletId | string | Either * | Wallet UUID |
| walletCode | string | Either * | 8-digit wallet code |
| phoneNumber | string | Required | Uganda phone 256XXXXXXXXX |
| amount | number | Required | Amount in UGX (1,000 – 5,000,000) |
| currency | string | Required | Must be "UGX" |
| accountReference | string | Required | Your order/invoice reference |
| transactionDesc | string | Optional | Payment description |
| callbackUrl | string | Optional | Webhook URL |
| metadata | object | Optional | Custom key-value pairs |
{
"walletCode": "87654321",
"phoneNumber": "256712345678",
"amount": 50000,
"currency": "UGX",
"accountReference": "ORDER-UG-001",
"transactionDesc": "Payment for groceries"
}Send Payment
Send money to a customer's mobile money account in Uganda.
/v1/payments/send-payment-uganda| Field | Type | Required | Description |
|---|---|---|---|
| walletId | string | Either * | Wallet UUID |
| walletCode | string | Either * | 8-digit wallet code |
| phoneNumber | string | Required | Recipient phone 256XXXXXXXXX |
| amount | number | Required | Amount in UGX (1,000 – 5,000,000) |
| currency | string | Required | Must be "UGX" |
| description | string | Required | Payment description (required) |
| callbackUrl | string | Optional | Webhook URL |
| metadata | object | Optional | Custom key-value pairs |
{
"walletCode": "87654321",
"phoneNumber": "256712345678",
"amount": 100000,
"currency": "UGX",
"description": "Freelance payment for March"
}Webhooks
When a transaction completes or fails, SparkPesa sends a signed HTTP POST to your callbackUrl.
Event Types
| Event | Description |
|---|---|
| payment.completed | Customer payment received and wallet credited |
| payment.failed | Customer payment failed |
| payout.completed | Money sent to recipient successfully |
| payout.failed | Payout to recipient failed |
Webhook Payload
{
"event": "payment.completed",
"transactionId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"reference": "STK-1710000000-abc123def",
"status": "completed",
"amount": 500,
"currency": "KES",
"timestamp": "2026-03-17T11:30:00.000Z",
"data": {
"phoneNumber": "254712345678",
"mpesaReceiptNumber": "UCA668UDWH",
"transactionDate": "20260317113000",
"accountReference": "INV-2026-001"
}
}Signature Verification
Always verify the X-SparkPesa-Signature header before processing:
const crypto = require('crypto');
app.post('/webhooks/sparkpesa', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-sparkpesa-signature'];
const rawBody = req.body.toString();
const expected = crypto
.createHmac('sha256', process.env.WEBHOOK_SECRET)
.update(rawBody).digest('hex');
if (!crypto.timingSafeEqual(
Buffer.from(signature, 'hex'),
Buffer.from(expected, 'hex')
)) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(rawBody);
switch (event.event) {
case 'payment.completed':
// Credit user, fulfill order
break;
case 'payout.completed':
// Payout confirmed
break;
}
res.status(200).json({ received: true });
});200 OK to acknowledge.Error Handling
HTTP Status Codes
| Code | Meaning |
|---|---|
200 | Success |
400 | Bad request / validation error |
401 | Invalid API key or signature |
403 | Insufficient permissions or wallet access denied |
404 | Wallet or resource not found |
429 | Rate limit exceeded |
500 | Server error |
Error Response Format
{
"error": "Validation failed",
"details": [
{ "field": "phoneNumber", "message": "Phone number must be in format 254XXXXXXXXX" },
{ "field": "amount", "message": "Amount must be greater than 0" }
]
}Common Errors
| Error | Cause | Fix |
|---|---|---|
| Missing X-Signature header | No HMAC signature | Add signature generation |
| Request timestamp is too old | Clock drift > 5 min | Sync system clock |
| Invalid API key or signature | Wrong key/secret/sig string | Check signature construction |
| Currency mismatch | Wallet currency ≠ request | Use matching wallet |
| Insufficient wallet balance | Not enough funds | Top up wallet balance |
| Access denied to specified wallet | API key scope | Check wallet permissions |
Code Examples
Full client SDK examples with authentication, all payment methods, and error handling.
import crypto from 'crypto';
class SparkPesaClient {
private apiKey: string;
private apiSecret: string;
private baseUrl: string;
constructor(apiKey: string, apiSecret: string, baseUrl = 'https://www.sparkpesa.io/api/v1') {
this.apiKey = apiKey;
this.apiSecret = apiSecret;
this.baseUrl = baseUrl;
}
private sign(payload: Record<string, any>) {
const timestamp = Math.floor(Date.now() / 1000);
const sigString = [
this.apiKey, timestamp,
payload.walletCode ?? '', payload.currency ?? '',
payload.phoneNumber ?? '', payload.amount ?? '',
payload.accountReference ?? '',
].join('');
const signature = crypto.createHmac('sha256', this.apiSecret)
.update(sigString).digest('hex');
return { signature, timestamp };
}
private async post(endpoint: string, payload: Record<string, any>) {
const { signature, timestamp } = this.sign(payload);
const res = await fetch(`${this.baseUrl}${endpoint}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${this.apiKey}`,
'X-Signature': signature,
'X-Timestamp': timestamp.toString(),
},
body: JSON.stringify(payload),
});
const data = await res.json();
if (!res.ok) throw new Error(data.error || 'Request failed');
return data;
}
// Collect M-Pesa payment
async requestPayment(walletCode: string, phone: string, amount: number, ref: string) {
return this.post('/payments/request-payment', {
walletCode, phoneNumber: phone, amount, currency: 'KES', accountReference: ref,
});
}
// Send M-Pesa B2C payout
async sendPayment(walletCode: string, phone: string, amount: number) {
return this.post('/payments/send-payment', {
walletCode, phoneNumber: phone, amount, currency: 'KES', commandId: 'BusinessPayment',
});
}
// Pay to Till Number
async buyGoods(walletCode: string, till: string, amount: number, ref: string, phone: string) {
return this.post('/payments/buy-goods', {
walletCode, tillNumber: till, amount, currency: 'KES', accountReference: ref, requesterPhone: phone,
});
}
// Pay to PayBill Number
async payBill(walletCode: string, paybill: string, acc: string, amount: number, phone: string) {
return this.post('/payments/paybill', {
walletCode, paybillNumber: paybill, accountNumber: acc, amount, currency: 'KES', requesterPhone: phone,
});
}
}
// Usage
const client = new SparkPesaClient('sk_live_...', 'ss_live_...');
const result = await client.requestPayment('12345678', '254712345678', 500, 'ORDER-001');
console.log('Transaction:', result.transactionId);Testing
Use sandbox credentials for testing. Sandbox transactions are simulated and do not interact with real payment providers.
Test Values
| Field | Kenya (KES) | Uganda (UGX) |
|---|---|---|
| Phone Number | 254708374149 | 256712345678 |
| Amount Range | 10 – 250,000 | 1,000 – 5,000,000 |
| Till Numbers | 247247, 518518 | N/A |
| PayBill | 320320 (acc: 12345678901) | N/A |
Testing Checklist
AI IDE Integration (MCP)
SparkPesa provides an MCP (Model Context Protocol) server that lets AI assistants in IDEs like Windsurf, Cursor, and VS Code (Copilot) interact with the SparkPesa API directly — collect payments, send payouts, generate code, and more, all from your AI chat.
Available Tools
| Tool | Description |
|---|---|
| request_payment_kenya | Collect M-Pesa payment via STK Push (KES) |
| send_payment_kenya | Send M-Pesa B2C payout (KES) |
| buy_goods | Pay to M-Pesa Till Number (KES) |
| paybill | Pay to M-Pesa PayBill Number (KES) |
| request_payment_uganda | Collect Mobile Money payment (UGX) |
| send_payment_uganda | Send Mobile Money payout (UGX) |
| list_wallets | List all wallets for your organization |
| get_wallet | Get wallet details and recent transactions |
| create_wallet | Create a new wallet (auto-generates 8-digit code) |
| update_wallet | Rename a wallet or update description/webhook URL |
| delete_wallet | Deactivate a wallet (soft-delete) |
| transfer_funds | Transfer funds between two wallets (same currency) |
| list_transactions | List paginated transactions for a wallet with filters |
| generate_signature | Generate HMAC signature for debugging |
| generate_webhook_handler | Generate webhook handler code (Express, Next.js, Flask, FastAPI) |
| generate_client_sdk | Generate full client SDK (TypeScript, JavaScript, Python, cURL) |
Setup
The MCP server is published on npm. No installation needed — your IDE runs it automatically via npx.
- 1Get your API Key and API Secret from Settings → API Keys
- 2Add the MCP server config to your IDE (see below)
- 3Restart your IDE — the SparkPesa tools will be available in AI chat
npm package
@sparkpesa/mcp-serverOr install globally: npm install -g @sparkpesa/mcp-server
IDE Configuration
Add to ~/.codeium/windsurf/mcp_config.json:
{
"mcpServers": {
"sparkpesa": {
"command": "npx",
"args": ["-y", "@sparkpesa/mcp-server"],
"env": {
"SPARKPESA_API_KEY": "sk_live_your_api_key",
"SPARKPESA_API_SECRET": "ss_live_your_api_secret"
}
}
}
}Usage Examples
Once configured, you can ask your AI assistant natural-language questions and it will use the SparkPesa tools:
Built-in Resources
The MCP server also exposes reference documentation as resources that AI assistants can read for context:
| Resource URI | Contents |
|---|---|
| sparkpesa://docs/overview | API overview, endpoints, authentication |
| sparkpesa://docs/webhooks | Webhook events, payloads, signature verification |
| sparkpesa://docs/errors | Error codes, troubleshooting guide |
SPARKPESA_API_KEY and SPARKPESA_API_SECRET in your IDE's MCP config. Optionally set SPARKPESA_BASE_URL to override the default API base URL.