API Documentation — SmartPay | M-Pesa STK Push Integration Guide

SmartPay API Reference

Integrate M-Pesa STK Push, manage API keys, query transactions, and send SMS — all from one authenticated REST API. No SDK required.

https://api.smartpaypesa.com/v1
REST / JSON
Bearer Token Auth
Rate Limited
Idempotent
Open Source SDK

Getting Started

SmartPay gives you a single endpoint to collect M-Pesa payments via STK Push, manage multiple API accounts, query transaction history, and send SMS — all authenticated with a Bearer API key.

Quickstart — live in 5 steps
1
Get your bootstrap key
Sign up at smartpaypesa.com/signup.php → open the API tab in your dashboard → copy your default API key. This is the only time you need the dashboard.
2
Create a dedicated key for your app
POST /v1/keys with a name. You get back a fresh api_key — store it in your secrets manager, never in code.
3
Set where payments land
PUT /v1/keys/{id}/destination — choose wallet, phone, paybill, till, or bank. Set your callback URL here too.
4
Optionally enable payment notifications
PUT /v1/keys/{id}/notifications — get email and/or SMS on every completed payment.
5
Send your first STK Push
POST /v1/stk/push with a phone and amount. The customer gets a prompt on their phone within seconds. Results arrive at your callback URL in real time.
PHP SDK on GitHub

Authentication

Every request must include your API key as a Bearer token in the Authorization header. Keys are 64 hex characters, cryptographically random.

http
Authorization: Bearer a3f8c1e9b2d4f6a0e7c5b8d1f3a9e2c6b4d7f0a2e5c8b1d3f6a9e0c2b5d8f1a4
Never expose your API key in client-side JavaScript, public repos, or logs. Rotate it immediately via POST /v1/keys/{id}/regenerate if compromised.

Base URL & Versioning

All endpoints are versioned under /v1/. We maintain backward compatibility within a major version.

base url
https://api.smartpaypesa.com/v1

All requests and responses use application/json. Every response includes a success boolean and, on errors, an error_code machine-readable string.

A diagnostic endpoint is available at GET /v1/ping — returns {"success":true,"message":"pong"} without authentication. Use it to verify connectivity.

API Key Management

Create and manage multiple API accounts — useful for separating production, staging, or multiple client integrations. Each account has its own key, rate limit, payment destination, and notification settings.

All endpoints
GET/v1/keysList all accounts
POST/v1/keysCreate account
GET/v1/keys/{id}Get account + key
DEL/v1/keys/{id}Delete account
POST/v1/keys/{id}/set-defaultSet as default
POST/v1/keys/{id}/regenerateRegenerate key
GET/v1/keys/{id}/destinationGet destination
PUT/v1/keys/{id}/destinationSet destination
PUT/v1/keys/{id}/notificationsSet notifications
List All Accounts — GET /v1/keys
GET /v1/keys
json — response
{
  "success": true,
  "accounts": [
    {
      "id": 1,
      "name": "Default Account",
      "is_default": true,
      "active": true,
      "limit": 4850,
      "fraud_suspended": false,
      "destination": { "method": "paybill", "paybill_number": "123456", "account_number": "ACC001" },
      "callbackurl": "https://myapp.com/callback",
      "notify_email": "pay@myapp.com",
      "notify_phone": "",
      "created_at": "2025-06-01 09:00:00",
      "last_used_at": "2025-06-03 14:22:11"
    }
  ],
  "count": 1
}
Create Account — POST /v1/keys
POST /v1/keys
FieldTypeRequiredDescription
namestringrequiredUnique label for this account (max 80 chars)
http — request
POST /v1/keys HTTP/1.1
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json

{ "name": "Production App" }
json — response 201
{
  "success": true,
  "message": "API account created successfully.",
  "account": {
    "id": 4,
    "name": "Production App",
    "is_default": false,
    "active": false,
    "limit": 0,
    "api_key": "a3f8c1e9b2d4f6a0e7c5b8d1f3a9e2c6b4d7f0a2e5c8b1d3f6a9e0c2b5d8f1a4",
    "destination": { "method": "wallet" },
    "callbackurl": "",
    "created_at": "2025-06-03 15:00:00"
  }
}
The api_key is returned in full only on create and regenerate. Store it securely — GET /v1/keys never includes it.
Set Payment Destination — PUT /v1/keys/{id}/destination
PUT /v1/keys/{id}/destination

Controls where incoming payments are routed. Choose one of five methods:

methodRequired extra fieldsDescription
walletPayments go to your SmartPay wallet
phonephoneM-Pesa number (2547XXXXXXXX)
paybillpaybill_number, account_numberPaybill business number + account
tilltill_numberBuy Goods till (5–7 digits)
bankpaybillbank_code, bank_accountBank paybill — see Supported Banks
Paybill example
json
{
  "method": "paybill",
  "paybill_number": "123456",
  "account_number": "ACC001",
  "callbackurl": "https://myapp.com/mpesa/callback"
}
Bank account example
json
{
  "method": "bankpaybill",
  "bank_code": "247247",
  "bank_account": "0123456789",
  "callbackurl": "https://myapp.com/mpesa/callback"
}
Notifications — PUT /v1/keys/{id}/notifications
PUT /v1/keys/{id}/notifications

Receive an email and/or SMS on every completed payment. Pass an empty string to disable either channel.

json — request body
{
  "notify_email": "payments@myapp.com",
  "notify_phone": "254712345678"
}

Initiate STK Push

Send an M-Pesa payment prompt to a customer's phone. Where the money goes is determined by the destination you configured on the API key. The customer sees the SmartPay prompt and enters their PIN.

The key must have an active subscription and remaining call limit > 0 before STK Push works.
POST /v1/stk/push
Request body
ParameterTypeRequiredDescription
phonestringrequiredCustomer phone — 2547XXXXXXXX, 2541XXXXXXXX, 07XX, or 01XX
amountintegerrequiredAmount in KES (1 – 300,000)
account_referencestringoptionalReference shown on M-Pesa prompt (default: SMARTPAY)
descriptionstringoptionalTransaction description (default: SmartPay Payment)
cURL example
bash
curl -X POST https://api.smartpaypesa.com/v1/stk/push \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{"phone":"254712345678","amount":150,"account_reference":"ORDER_101"}'
PHP example
php
<?php
$ch = curl_init('https://api.smartpaypesa.com/v1/stk/push');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => [
        'Content-Type: application/json',
        'Authorization: Bearer ' . $YOUR_API_KEY,
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'phone'  => '254712345678',
        'amount' => 150,
    ]),
]);
$res = json_decode(curl_exec($ch));
// $res->checkout_request_id — poll or wait for callback
Success response
json
{
  "success": true,
  "message": "STK Push initiated successfully.",
  "checkout_request_id": "ws_CO_03062025160832822727856009",
  "merchant_request_id": "aea5-4f44-b4a1-d9044446ee461957335"
}
Save checkout_request_id — use it to query GET /v1/transactions/{ref} or match incoming webhook callbacks.
Rate limits
LimitValueResponse header
Per IP200 req / min
Per key (minute)60 req / minX-RateLimit-Remaining
Per key (day)10,000 req / dayX-RateLimit-Day-Remaining
Per phone number3 pushes per 5 min

Transaction Status

Query a payment by its checkout_request_id. Prefer webhooks for real-time results — use this for reconciliation or as a fallback.

GET/v1/transactions/{checkout_request_id}
http
GET /v1/transactions/ws_CO_03062025142200123456789 HTTP/1.1
Authorization: Bearer YOUR_API_KEY
List transactions — GET /v1/transactions
GET/v1/transactions
Query paramDefaultDescription
limit20Records per page (max 100)
offset0Skip N records for pagination
statusFilter: pending, completed, or failed

Account Balance

Retrieve your SmartPay wallet balance and account info.

GET/v1/account
json — response
{
  "success": true,
  "account": {
    "account_number": "SP36413506",
    "fullname": "John Kamau",
    "verified": true
  },
  "active_key": {
    "id": 4,
    "name": "Production App",
    "plan": "Professional",
    "limit_remaining": 4850,
    "fraud_suspended": false
  },
  "subscription": {
    "plan": "Professional",
    "status": "active",
    "expires_at": "2025-07-01 00:00:00",
    "api_calls": 5000
  },
  "transaction_stats": {
    "total": 312, "completed": 298, "pending": 4, "failed": 10
  }
}

SMS API

Send single or bulk SMS to Kenyan numbers. Uses the X-API-Key header. Each 160-character block costs 1 credit.

Send SMS — POST /v1/sms/
POST/v1/sms/
ParameterRequiredDescription
phonerequiredSupports 07XX, 01XX, or 254XX formats
messagerequiredSMS content — auto-split at 160 chars
bash
curl -X POST https://api.smartpaypesa.com/v1/sms/ \
  -H "Content-Type: application/json" \
  -H "X-API-Key: YOUR_API_KEY" \
  -d '{"phone":"0712345678","message":"Hello from SmartPay!"}'
json — response
{
  "status": "success",
  "data": {
    "transaction_id": "5713595232279350695",
    "mobile": "254712345678",
    "sms_parts": 1,
    "characters": 22,
    "cost": 1,
    "remaining_balance": 99
  }
}
Check SMS balance — GET /v1/sms/balance
GET/v1/sms/balance
SMS pricing
Message lengthSMS partsCredits
1–160 chars1 SMS1 credit
161–320 chars2 SMS2 credits
321–480 chars3 SMS3 credits
481–640 chars4 SMS4 credits

Webhooks

SmartPay POSTs real-time payment results to your callback URL the moment a transaction completes — no polling required.

Configuration
  1. Include callbackurl in your PUT /v1/keys/{id}/destination call.
  2. Or set it in the dashboard: API → select account → destination → Callback URL.
Your endpoint must return HTTP 200 within 5 seconds. SmartPay retries failed deliveries up to 3 times with exponential back-off.
Webhook payload
json — POST body to your server
{
  "Body": {
    "stkCallback": {
      "MerchantRequestID": "aea5-4f44-b4a1-d9044446ee463545184",
      "CheckoutRequestID": "ws_CO_17062025005554749727856009",
      "ResultCode": 0,
      "ResultDesc": "The service request is processed successfully.",
      "CallbackMetadata": {
        "Item": [
          { "Name": "Amount",             "Value": 150 },
          { "Name": "MpesaReceiptNumber", "Value": "TFH774CBH1" },
          { "Name": "TransactionDate",    "Value": 20250617005603 },
          { "Name": "PhoneNumber",        "Value": 254712345678 }
        ]
      }
    }
  }
}
Always check ResultCode === 0 before crediting an account. A callback with ResultCode: 1032 means the customer cancelled. 1032, 1037, and 9999 are the most common non-zero codes.

Error Codes

API errors
error_codeHTTPMeaning
MISSING_AUTH401Authorization header missing
INVALID_API_KEY401Key not found, wrong format, or inactive
KEY_SUSPENDED403Suspended due to fraud activity
KEY_INACTIVE403Subscription not active on this key
LIMIT_REACHED403Monthly API call limit exhausted
MISSING_FIELDS400Required fields missing from body
INVALID_PHONE400Phone number format invalid
INVALID_AMOUNT400Amount out of range (1–300,000)
DUPLICATE_NAME409Account with that name already exists
CANNOT_DELETE_DEFAULT400Set another key as default first
IP_RATE_LIMIT429Too many requests from this IP
RATE_LIMIT_MINUTE429Per-key per-minute limit hit
RATE_LIMIT_DAY429Per-key daily limit hit
PHONE_RATE_LIMIT4293 pushes already sent to this number in 5 min
FRAUD_BLOCK_FAILURE_RATE403Abnormally high failure rate detected
FRAUD_BLOCK_PHONE_DIVERSITY403Suspicious random-phone pattern detected
FRAUD_BLOCK_BURST429High-volume burst + high failure rate
FRAUD_BLOCK_CANCEL_RATE403High customer rejection/cancel rate
PAYMENT_FAILURE500Daraja rejected the STK Push
NOT_FOUND404Resource or endpoint does not exist
M-Pesa ResultCodes
ResultCodeMeaning
0Success — payment accepted
1Insufficient balance
1001Unable to lock subscriber — another transaction in progress
1019Transaction has expired
1032Cancelled by user
1037DS timeout — user cannot be reached
2001Initiator information is invalid
9999General error while sending push

Supported Banks

Use these bank_code values when setting a bankpaybill destination.

bank_codeBank name
111777ABC Bank
303030Absa Bank
972900Bank of Africa
247247Equity Bank
222111Family Bank Ltd
542542I&M Bank
522522KCB (522522)
522533KCB (522533)
547700National Bank
488488NIC Bank
600100Stanbic Bank
329329Standard Chartered Bank
516600Diamond Trust Bank (DTB)
700201Eco Bank
400200Co-operative Bank (400200)
400222Co-operative Bank (400222)
100400Housing Finance (HFC)
4156839SMARTPAY WALLET
200999Post Office Savings Bank
982800Prime Bank
559900UBA Bank
888600Century Microfinance Bank
508400Consolidated Bank
972700Credit Bank
498100Equatorial Commercial Bank
328585Faulu Microfinance Bank
919700First Community Bank
344500Guardian Bank
985050Gulf African Bank
529901Jamii Bora Bank
910200JT Bank
111999K-Rep Bank
101200Kenya Women Microfinance Bank
333222Mkopa
514000Musoni Microfinance
802200Rafiki Microfinance Bank
777001SMEP Microfinance Bank
862862Transnational Bank
504600Uwezo Microfinance Bank
200555Vision Fund Kenya

Best Practices

Security
  • Always use HTTPS for all API requests.
  • Never expose your API key in client-side code, logs, or public repositories. Rotate via POST /v1/keys/{id}/regenerate if compromised.
  • Create separate keys for production, staging, and each client — use POST /v1/keys.
  • Validate all webhook payloads — check ResultCode === 0 before crediting an account.
Performance
  • Use webhooks instead of polling GET /v1/transactions/{ref}.
  • Respond to webhook requests immediately with HTTP 200 and process asynchronously.
  • Implement exponential back-off for 429 responses.
Phone & retry logic
  • Handle PHONE_RATE_LIMIT (3 pushes / 5 min per number) — back off for at least 5 minutes before retrying the same number.
  • Store checkout_request_id from every STK Push for later reconciliation.
  • Keep SMS messages under 160 characters to use 1 credit per message.
On first deploy: create a dedicated key via POST /v1/keys and store it in your secrets manager — never use the bootstrap key in production code.