Skip to content

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 

Repository files navigation

M-Pesa Daraja API — STK Push Documentation

Author: Simon Nduati Ngigi
GitHub: symo101
Last Updated: April 2026


Table of Contents


Overview

The M-Pesa Express API (commonly known as STK Push or Lipa Na M-Pesa Online) is a Merchant/Business-initiated C2B (Customer to Business) payment API offered by Safaricom through the Daraja developer portal.

Instead of asking a customer to manually enter a Paybill number and account, the business triggers a payment prompt directly to the customer's phone. The customer only needs to enter their M-Pesa PIN to complete the transaction.

Key benefits:

  • Reduces wrong payments caused by manual entry errors
  • Faster checkout experience for customers
  • Real-time payment confirmation via callback URL

How It Works

Business Server  →  Daraja API  →  Customer's Phone
                                         ↓
                              Customer enters M-Pesa PIN
                                         ↓
Callback URL  ←  Daraja API  ←  M-Pesa Processes Transaction
  1. Your server sends a POST request to Daraja with the customer's phone number and amount.
  2. Daraja validates the request and sends an STK Push prompt to the customer's phone.
  3. The customer enters their M-Pesa PIN (or cancels).
  4. Daraja sends the transaction result to your CallBackURL.

Note: The customer's phone must be online and unlocked to receive the STK prompt.


Base URLs

Environment Base URL
Sandbox https://sandbox.safaricom.co.ke
Production https://api.safaricom.co.ke

Authentication

All Daraja API requests require a Bearer token obtained from the OAuth endpoint.

Get Access Token

Endpoint:

GET /oauth/v1/generate?grant_type=client_credentials

Authorization: Basic Auth using your Consumer Key and Consumer Secret (from your Daraja app dashboard).

Request Example:

curl -X GET \
  "https://sandbox.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials" \
  -H "Authorization: Basic BASE64(ConsumerKey:ConsumerSecret)"

Response:

{
  "access_token": "SGWcJPtNtYNPGm0uCYBCJKFVKJHFKJHF",
  "expires_in": "3599"
}

The token expires in 3599 seconds (~1 hour). Generate a new one before it expires.


Endpoint: STK Push Request

Initiates the payment prompt on the customer's phone.

Endpoint:

POST /mpesa/stkpush/v1/processrequest

Headers:

Content-Type: application/json
Authorization: Bearer {access_token}

Request Body

Field Type Required Description
BusinessShortCode String Yes The organization's shortcode (Paybill or Till Number)
Password String Yes Base64 encoded string of BusinessShortCode + Passkey + Timestamp
Timestamp String Yes Transaction timestamp in format yyyymmddhhiiss
TransactionType String Yes Use CustomerPayBillOnline for Paybill or CustomerBuyGoodsOnline for Till
Amount Integer Yes Amount to charge the customer
PartyA String Yes Customer's phone number (format: 2547XXXXXXXX)
PartyB String Yes Your Paybill or Till Number
PhoneNumber String Yes Same as PartyA — the phone receiving the STK prompt
CallBackURL String Yes HTTPS URL where Daraja will send the transaction result
AccountReference String Yes Reference shown to customer on the STK screen (max 12 chars)
TransactionDesc String Yes Short description of the transaction (max 13 chars)

Generating the Password

import base64
from datetime import datetime

shortcode = "174379"
passkey = "YOUR_PASSKEY"
timestamp = datetime.now().strftime("%Y%m%d%H%M%S")

password = base64.b64encode(
    (shortcode + passkey + timestamp).encode("utf-8")
).decode("utf-8")

Request Example

{
  "BusinessShortCode": "174379",
  "Password": "MTc0Mzc5YmZiMjc5ZjlhYTliZGJjZjE1...",
  "Timestamp": "20260415120000",
  "TransactionType": "CustomerPayBillOnline",
  "Amount": 1,
  "PartyA": "254712345678",
  "PartyB": "174379",
  "PhoneNumber": "254712345678",
  "CallBackURL": "https://yourdomain.com/mpesa/callback",
  "AccountReference": "Order001",
  "TransactionDesc": "Payment for order"
}

Success Response

{
  "MerchantRequestID": "29115-34620561-1",
  "CheckoutRequestID": "ws_CO_191220191020363925",
  "ResponseCode": "0",
  "ResponseDescription": "Success. Request accepted for processing",
  "CustomerMessage": "Success. Request accepted for processing"
}

Important: A ResponseCode: "0" only means the request was accepted, not that payment was completed. The actual result comes via the CallBackURL.


Endpoint: STK Push Query

Use this to check the status of an STK Push transaction — especially useful when a callback was not received.

Endpoint:

POST /mpesa/stkpushquery/v1/query

Headers:

Content-Type: application/json
Authorization: Bearer {access_token}

Request Body

Field Type Required Description
BusinessShortCode String Yes Your organization's shortcode
Password String Yes Same password generation as STK Push request
Timestamp String Yes Same timestamp used in STK Push request
CheckoutRequestID String Yes The CheckoutRequestID from the STK Push response

Request Example

{
  "BusinessShortCode": "174379",
  "Password": "MTc0Mzc5YmZiMjc5ZjlhYTliZGJjZjE1...",
  "Timestamp": "20260415120000",
  "CheckoutRequestID": "ws_CO_191220191020363925"
}

Response

{
  "ResponseCode": "0",
  "ResponseDescription": "The service request has been accepted successfully",
  "MerchantRequestID": "29115-34620561-1",
  "CheckoutRequestID": "ws_CO_191220191020363925",
  "ResultCode": "0",
  "ResultDesc": "The service request is processed successfully."
}

Callback Response

After the customer completes or cancels the STK Push, Daraja sends a POST request to your CallBackURL.

Successful Payment Callback

{
  "Body": {
    "stkCallback": {
      "MerchantRequestID": "29115-34620561-1",
      "CheckoutRequestID": "ws_CO_191220191020363925",
      "ResultCode": 0,
      "ResultDesc": "The service request is processed successfully.",
      "CallbackMetadata": {
        "Item": [
          { "Name": "Amount", "Value": 1.00 },
          { "Name": "MpesaReceiptNumber", "Value": "NLJ7RT61SV" },
          { "Name": "TransactionDate", "Value": 20191219102115 },
          { "Name": "PhoneNumber", "Value": 254712345678 }
        ]
      }
    }
  }
}

Failed / Cancelled Callback

{
  "Body": {
    "stkCallback": {
      "MerchantRequestID": "29115-34620561-1",
      "CheckoutRequestID": "ws_CO_191220191020363925",
      "ResultCode": 1032,
      "ResultDesc": "Request cancelled by user."
    }
  }
}

When ResultCode is 0, the payment was successful. Any other value means it failed — check ResultDesc for the reason.


Error Codes

Result Code Description
0 Success
1 Insufficient funds
1001 Unable to lock subscriber — try again later
1019 Transaction expired — user took too long
1032 Request cancelled by user
1037 DS timeout — user's phone was unreachable
2001 Wrong PIN entered
17 System internal error — contact Safaricom support

Code Examples

Python (requests)

import requests
import base64
from datetime import datetime

# Step 1: Get access token
def get_access_token(consumer_key, consumer_secret):
    url = "https://sandbox.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials"
    response = requests.get(url, auth=(consumer_key, consumer_secret))
    return response.json()["access_token"]

# Step 2: Generate password
def generate_password(shortcode, passkey):
    timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
    raw = shortcode + passkey + timestamp
    password = base64.b64encode(raw.encode()).decode()
    return password, timestamp

# Step 3: Initiate STK Push
def stk_push(access_token, phone_number, amount):
    shortcode = "174379"
    passkey = "YOUR_PASSKEY"
    password, timestamp = generate_password(shortcode, passkey)

    url = "https://sandbox.safaricom.co.ke/mpesa/stkpush/v1/processrequest"
    headers = {"Authorization": f"Bearer {access_token}", "Content-Type": "application/json"}

    payload = {
        "BusinessShortCode": shortcode,
        "Password": password,
        "Timestamp": timestamp,
        "TransactionType": "CustomerPayBillOnline",
        "Amount": amount,
        "PartyA": phone_number,
        "PartyB": shortcode,
        "PhoneNumber": phone_number,
        "CallBackURL": "https://yourdomain.com/mpesa/callback",
        "AccountReference": "Order001",
        "TransactionDesc": "Payment"
    }

    response = requests.post(url, json=payload, headers=headers)
    return response.json()

JavaScript (Node.js / fetch)

const axios = require("axios");
const btoa = require("btoa");

async function getAccessToken(consumerKey, consumerSecret) {
  const credentials = btoa(`${consumerKey}:${consumerSecret}`);
  const res = await axios.get(
    "https://sandbox.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials",
    { headers: { Authorization: `Basic ${credentials}` } }
  );
  return res.data.access_token;
}

async function stkPush(accessToken, phoneNumber, amount) {
  const shortcode = "174379";
  const passkey = "YOUR_PASSKEY";
  const timestamp = new Date().toISOString().replace(/[-T:.Z]/g, "").slice(0, 14);
  const password = btoa(shortcode + passkey + timestamp);

  const res = await axios.post(
    "https://sandbox.safaricom.co.ke/mpesa/stkpush/v1/processrequest",
    {
      BusinessShortCode: shortcode,
      Password: password,
      Timestamp: timestamp,
      TransactionType: "CustomerPayBillOnline",
      Amount: amount,
      PartyA: phoneNumber,
      PartyB: shortcode,
      PhoneNumber: phoneNumber,
      CallBackURL: "https://yourdomain.com/mpesa/callback",
      AccountReference: "Order001",
      TransactionDesc: "Payment"
    },
    { headers: { Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json" } }
  );

  return res.data;
}

Testing in Sandbox

  1. Create an account at developer.safaricom.co.ke
  2. Create a new Sandbox app and note your Consumer Key and Consumer Secret
  3. Use the sandbox shortcode 174379 and the test passkey from your dashboard
  4. Use a Safaricom number you own as PhoneNumber — you will receive the actual STK prompt
  5. Use ngrok or Webhook.site to expose a local callback URL for testing

Sandbox Test Credentials:
Shortcode: 174379
Passkey: Available in your Daraja sandbox app dashboard


Go Live Checklist

  • Registered business account on Daraja portal
  • Production app created and approved
  • CallBackURL uses HTTPS (required by Safaricom)
  • Callback endpoint validates ResultCode before marking payment as successful
  • Duplicate transaction handling implemented (check MpesaReceiptNumber)
  • STK Query fallback implemented for missed callbacks
  • Rate limiting on payment initiation endpoint
  • Access token refresh logic implemented (token expires in ~1 hour)

Resources


Documentation by Simon Nduati Ngigi — GitHub · LinkedIn

About

API documentation for Safaricom M-Pesa Daraja STK Push

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors