Introduction
The Daraja M-PESA Gateway is a unified REST API that simplifies integration with Safaricom's M-PESA Daraja platform. Instead of managing OAuth tokens, security credentials, callback URLs, and complex payload structures yourself, you authenticate with a single API key and send straightforward JSON requests. The gateway handles everything else.
What you can do
- STK Push — Trigger a payment prompt on a customer's phone.
- STK Status — Poll the payment status of an STK Push transaction.
- STK Query — Check the status of an STK Push request.
- B2C — Send money from your business to a customer.
- Reversal — Reverse a completed M-PESA transaction.
- QR Code — Generate a dynamic M-PESA QR code for payments.
- Transaction Status — Look up the status of any transaction.
- Standing Order — Create recurring M-PESA payment instructions (M-PESA Ratiba).
- Pull Transactions — Query C2B transactions performed under a shortcode within the last 48 hours.
Account Balance queries allow you to check your M-PESA organization balance. This endpoint requires a valid API key and appropriate permissions.
Base URL
https:///gateway/v1/
All endpoints accept POST requests with a JSON body and return JSON
responses. Every response includes rate-limit metadata in both HTTP headers and the
response body.
Response envelope
Every successful response follows this structure:
{
"success": true,
"data": {
// endpoint-specific payload
},
"meta": {
"rate_limit": {
"limit": 50,
"remaining": 49,
"reset": 1711929600
}
}
}
Every error response follows this structure:
{
"success": false,
"error": {
"code": 401,
"message": "Invalid API key."
}
}
Authentication
All API requests must include a valid API key. Keys are tied to your user account and can be created, rotated, or revoked from the dashboard.
Getting your API key
- Register or log in at /user.
- Navigate to API Keys in your dashboard.
- Click Generate New Key. Your key will be displayed once — copy it immediately.
- Store the key securely. Do not expose it in client-side code or public repositories.
Sending the key
Include your key in the X-Api-Key request header:
X-Api-Key: your_api_key_here
Alternatively, you can use the Authorization header with the Bearer scheme:
Authorization: Bearer your_api_key_here
If both headers are present, X-Api-Key takes precedence. If neither
header is provided, the gateway returns 401 Unauthorized.
Key lifecycle
You can have multiple active keys. Each key independently tracks its last-used
timestamp. Revoking a key is immediate and permanent — any in-flight request
using that key will fail with 401.
Rate Limits
Requests are rate-limited on a per-user, per-day basis. The limit depends on your account tier. Rate counters reset at midnight (server time) each day.
| Tier | Daily Limit | Description |
|---|---|---|
| Free | 50 | Default tier for new accounts. Suitable for development and testing. |
| Basic | 500 | For small applications in production. |
| Pro | Unlimited | No daily cap. For high-volume production workloads. |
Response headers
Every API response includes these rate-limit headers:
| Header | Type | Description |
|---|---|---|
| X-RateLimit-Limit | integer | "unlimited" | Maximum number of requests allowed per day for your tier. |
| X-RateLimit-Remaining | integer | "unlimited" | Number of requests remaining in the current window. |
| X-RateLimit-Reset | integer | Unix timestamp (UTC) when the rate-limit window resets (midnight). |
Exceeding the limit
When you exhaust your daily quota, the API returns 429 Too Many Requests
with a JSON error body. The X-RateLimit-Reset header tells you when you
can resume making requests.
{
"success": false,
"error": {
"code": 429,
"message": "Rate limit exceeded (50/day for free tier). Upgrade for more requests."
}
}
Endpoints
The gateway exposes nine endpoints, all under /gateway/v1/ or
/daraja/api/. Every
endpoint requires POST with a Content-Type: application/json
body and a valid API key.
STK Push
Initiates a Lipa Na M-PESA Online (STK Push) prompt on the customer's phone. The customer enters their M-PESA PIN to authorize the payment.
Request Headers
| Header | Required | Description |
|---|---|---|
| Content-Type | Required | Must be application/json |
| X-Api-Key | Required | Your API key |
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
| phone | string | Required | Customer phone number. Accepts multiple formats: 254XXXXXXXXX, 0XXXXXXXXX, or XXXXXXXXX (9 digits). Whitespace is stripped automatically. The gateway normalizes all formats to 254XXXXXXXXX. |
| amount | integer | Required | Amount in KES. Must be >= 1. |
| reference | string | Optional | Account reference / order ID displayed on the STK prompt. Default: "Payment". |
| description | string | Optional | Human-readable description of the transaction. Default: "Payment". |
Example Request
{
"phone": "254712345678",
"amount": 100,
"reference": "ORD-10042",
"description": "Payment for order #10042"
}
Responses
{
"success": true,
"data": {
"MerchantRequestID": "29115-34620561-1",
"CheckoutRequestID": "ws_CO_191220191020363925",
"ResponseCode": "0",
"ResponseDescription": "Success. Request accepted for processing",
"CustomerMessage": "Success. Request accepted for processing",
"statusCheck": {
"checkoutRequestId": "ws_CO_191220191020363925",
"endpoint": "gateway/v1/stk_status.php",
"method": "POST",
"polling": "Poll every 3-5 seconds for up to 2 minutes."
}
},
"meta": {
"rate_limit": {
"limit": 50,
"remaining": 49,
"reset": 1711929600
}
}
}
{
"success": false,
"error": {
"code": 400,
"message": "Missing required parameter: phone"
}
}
An STK Push is asynchronous — the customer receives a prompt on their phone and may
take time to respond. After a successful STK Push request, use the
checkoutRequestId from the statusCheck object to poll the
STK Status endpoint every 3–5 seconds. The status will
update from pending to success or failed once the
customer responds (or the request times out).
STK Status
Checks the payment status of an STK Push transaction using the
CheckoutRequestID returned from the STK Push response. This queries
the gateway's local database (populated by M-PESA callbacks) rather than calling
Safaricom directly, so it does not count against your rate limit.
1. Send an STK Push request.
2. Extract checkoutRequestId from the statusCheck object in the response.
3. Poll this endpoint every 3–5 seconds.
4. Stop polling when status is "success" or "failed" (or after ~2 minutes timeout).
Request Headers
| Header | Required | Description |
|---|---|---|
| Content-Type | Required | Must be application/json |
| X-Api-Key | Required | Your API key. You can only check transactions initiated with your own API key. |
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
| checkoutRequestId | string | Required | The CheckoutRequestID returned from the STK Push response. |
Example Request
{
"checkoutRequestId": "ws_CO_191220191020363925"
}
Responses
{
"success": true,
"data": {
"checkoutRequestId": "ws_CO_191220191020363925",
"status": "success",
"receipt": "UC4C38D22C",
"resultCode": "0",
"resultDesc": "The service request is processed successfully.",
"amount": 100,
"phone": "254712345678",
"createdAt": "2026-03-04 14:30:00"
},
"meta": {
"rate_limit": {
"limit": 50,
"remaining": 48,
"reset": 1711929600
}
}
}
{
"success": true,
"data": {
"checkoutRequestId": "ws_CO_191220191020363925",
"status": "pending",
"receipt": null,
"resultCode": null,
"resultDesc": null,
"amount": 100,
"phone": "254712345678",
"createdAt": "2026-03-04 14:30:00"
},
"meta": { ... }
}
{
"success": true,
"data": {
"checkoutRequestId": "ws_CO_191220191020363925",
"status": "failed",
"receipt": null,
"resultCode": "1032",
"resultDesc": "Request cancelled by user.",
"amount": 100,
"phone": "254712345678",
"createdAt": "2026-03-04 14:30:00"
},
"meta": { ... }
}
{
"success": false,
"error": {
"code": 404,
"message": "Transaction not found or does not belong to your account."
}
}
STK Query
Queries the status of a previously initiated STK Push request. Use this to confirm whether the customer completed, cancelled, or ignored the payment prompt.
STK Status checks the gateway's local database (fast, no Safaricom call). STK Query queries Safaricom directly and counts against your rate limit. For most use cases, prefer STK Status for polling after an STK Push.
Request Headers
| Header | Required | Description |
|---|---|---|
| Content-Type | Required | Must be application/json |
| X-Api-Key | Required | Your API key |
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
| checkoutRequestId | string | Required | The CheckoutRequestID returned from the STK Push response. |
Example Request
{
"checkoutRequestId": "ws_CO_191220191020363925"
}
Responses
{
"success": true,
"data": {
"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."
},
"meta": {
"rate_limit": {
"limit": 50,
"remaining": 48,
"reset": 1711929600
}
}
}
{
"success": false,
"error": {
"code": 400,
"message": "Missing required parameter: checkoutRequestId"
}
}
B2C Payment
Sends money from your M-PESA business account directly to a customer's M-PESA wallet. Commonly used for salary payments, cashback, refunds, and promotional disbursements.
Request Headers
| Header | Required | Description |
|---|---|---|
| Content-Type | Required | Must be application/json |
| X-Api-Key | Required | Your API key |
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
| phone | string | Required | Recipient phone number. Accepts 254XXXXXXXXX, 0XXXXXXXXX, or XXXXXXXXX (9 digits). Whitespace stripped automatically. |
| amount | integer | Required | Amount in KES. Must be >= 1. |
| commandId | string | Optional | Type of B2C transaction. One of: BusinessPayment, SalaryPayment, PromotionPayment. Default: "BusinessPayment". |
| remarks | string | Optional | Comments sent with the transaction. Default: "Payment". |
| occasion | string | Optional | Optional occasion or reason for the payment. |
Example Request
{
"phone": "254712345678",
"amount": 500,
"commandId": "BusinessPayment",
"remarks": "Refund for order #10042",
"occasion": "Customer refund"
}
Responses
{
"success": true,
"data": {
"ConversationID": "AG_20191219_00004492b1b6f0de8f9c",
"OriginatorConversationID": "16740-34861180-1",
"ResponseCode": "0",
"ResponseDescription": "Accept the service request successfully."
},
"meta": {
"rate_limit": {
"limit": 500,
"remaining": 499,
"reset": 1711929600
}
}
}
{
"success": false,
"error": {
"code": 400,
"message": "Invalid commandId. Allowed: BusinessPayment, SalaryPayment, PromotionPayment"
}
}
Account Balance Admin Only
Queries the balance of the M-PESA business account associated with your gateway configuration. This endpoint is restricted to administrators and is not available through the API gateway for regular API users.
This endpoint returns 403 Forbidden when called through the API gateway.
Account balance queries can only be performed from the admin dashboard.
Request Headers
| Header | Required | Description |
|---|---|---|
| Content-Type | Required | Must be application/json |
| X-Api-Key | Required | Your API key |
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
| remarks | string | Optional | Comments for the balance query. Default: "Balance check". |
Example Request
{
"remarks": "End-of-day balance check"
}
Responses
{
"success": true,
"data": {
"ConversationID": "AG_20191219_00004492b1b6f08c2e88",
"OriginatorConversationID": "16740-34861134-1",
"ResponseCode": "0",
"ResponseDescription": "Accept the service request successfully."
},
"meta": {
"rate_limit": {
"limit": 50,
"remaining": 47,
"reset": 1711929600
}
}
}
{
"success": false,
"error": {
"code": 502,
"message": "Upstream M-PESA service unavailable. Try again later."
}
}
Reversal
Reverses a completed M-PESA transaction. The funds are returned to the sender's account. This is typically used to correct erroneous payments.
Request Headers
| Header | Required | Description |
|---|---|---|
| Content-Type | Required | Must be application/json |
| X-Api-Key | Required | Your API key |
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
| transactionId | string | Required | The M-PESA transaction ID to reverse (e.g. OEI2AK4Q16). |
| amount | integer | Required | Amount to reverse in KES. Must be >= 1. |
| remarks | string | Optional | Reason for the reversal. |
| occasion | string | Optional | Optional occasion for the reversal. |
Example Request
{
"transactionId": "OEI2AK4Q16",
"amount": 100,
"remarks": "Wrong recipient",
"occasion": "Correction"
}
Responses
{
"success": true,
"data": {
"ConversationID": "AG_20191219_00005c92cafb81236f16",
"OriginatorConversationID": "16740-34861299-1",
"ResponseCode": "0",
"ResponseDescription": "Accept the service request successfully."
},
"meta": {
"rate_limit": {
"limit": 50,
"remaining": 46,
"reset": 1711929600
}
}
}
{
"success": false,
"error": {
"code": 400,
"message": "Missing required parameter: transactionId"
}
}
QR Code
Generates an M-PESA dynamic QR code. The customer scans the code with the M-PESA app to complete the payment. Useful for in-store and point-of-sale scenarios.
Request Headers
| Header | Required | Description |
|---|---|---|
| Content-Type | Required | Must be application/json |
| X-Api-Key | Required | Your API key |
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
| merchantName | string | Optional | Name of the merchant displayed on the QR code. |
| reference | string | Optional | Payment reference or order number. |
| amount | integer | Required | Amount in KES. Must be >= 1. |
| transactionType | string | Optional | QR transaction type code. Default: "BG" (Buy Goods). |
Example Request
{
"merchantName": "Acme Store",
"reference": "INV-2024-001",
"amount": 250,
"transactionType": "BG"
}
Responses
{
"success": true,
"data": {
"ResponseCode": "0",
"RequestID": "QR-1001",
"ResponseDescription": "QR code generated successfully.",
"QRCode": "data:image/png;base64,iVBOR..."
},
"meta": {
"rate_limit": {
"limit": 50,
"remaining": 45,
"reset": 1711929600
}
}
}
{
"success": false,
"error": {
"code": 400,
"message": "Missing required parameter: amount (must be >= 1)"
}
}
Transaction Status
Queries the status of any M-PESA transaction by its transaction ID. Use this endpoint to verify whether a payment was completed, failed, or is still pending.
This endpoint is available but may require additional server-side configuration
depending on your deployment. Contact the administrator if you receive a
501 response.
Request Headers
| Header | Required | Description |
|---|---|---|
| Content-Type | Required | Must be application/json |
| X-Api-Key | Required | Your API key |
| X-Api-Secret | Required | Your API secret |
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
| transactionId | string | Required | The M-PESA receipt/reference code (e.g. UHQC348YHN). |
| partyA | string | Optional | Your Organization ShortCode (e.g. 9579226 or 3534519). Defaults to system shortcode. |
| identifierType | string | Optional | 1 (MSISDN), 2 (Till Number), 4 (ShortCode - default), 11 (Reversal Party). |
| remarks | string | Optional | Query reason/remarks (e.g. Status Check). |
Example Request
{
"transactionId": "UHQC348YHN",
"partyA": "9579226",
"identifierType": "4",
"remarks": "Status Check"
}
Responses
{
"success": true,
"data": {
"ResponseCode": "0",
"ResponseDescription": "Accept the service request successfully.",
"ConversationID": "AG_20191219_00005e83b2effadd5b6a",
"OriginatorConversationID": "16740-34861444-1"
},
"meta": {
"rate_limit": {
"limit": 50,
"remaining": 44,
"reset": 1711929600
}
}
}
{
"success": false,
"error": {
"code": 400,
"message": "Missing required parameter: transactionId"
}
}
B2Pochi (Business Pay to Pochi)
The B2Pochi API allows a business to send funds from its M-PESA Business account directly to a Pochi la Biashara MSISDN. This is ideal for businesses paying small-scale vendors or service providers who use Pochi.
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
| partyA | string | Required | The sending Business Shortcode. |
| partyB | string | Required | The receiving Pochi MSISDN (e.g., 2547XXXXXXXX). |
| amount | integer | Required | Amount to send in KES. |
| accountReference | string | Optional | Default: B2Pochi. |
Bill Manager
Safaricom Bill Manager is an invoicing and payment reconciliation service. It allows businesses to send invoices via SMS/Email and receive payments directly against those invoices.
- Onboarding:
/api/billmanager_onboarding.php - Single Invoice:
/api/billmanager_single_invoice.php - Bulk Invoice:
/api/billmanager_bulk_invoice.php - Reconciliation:
/api/billmanager_reconciliation.php
Example: Single Invoice
{
"externalReference": "INV-101",
"billedFullName": "John Doe",
"billedPhoneNumber": "254712345678",
"amount": 1500,
"dueDate": "2026-06-30",
"accountReference": "ACC-001"
}
C2B v2 (Paybill/Till)
The Customer to Business (C2B) API allows you to receive real-time notifications when a customer pays into your Paybill or Till number. Unlike STK Push, the transaction is initiated by the customer from their M-PESA menu.
1. URL Registration: Register your Confirmation and Validation URLs (one-time).
2. Simulation (Sandbox only): Trigger a test payment to verify your URLs are working.
1. URL Registration
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
| shortCode | string | Required | The Paybill or Till number to register. |
| responseType | string | Optional | Default action if ValidationURL is down. Completed (default) or Cancelled. |
| confirmationUrl | string | Optional | URL for final payment notifications. Defaults to gateway callback. |
| validationUrl | string | Optional | URL to validate account numbers before payment. Defaults to gateway callback. |
2. C2B Simulation
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
| shortCode | string | Required | The receiving Paybill or Till number. |
| amount | integer | Required | The amount to simulate. |
| msisdn | string | Required | The phone number of the "customer" (e.g., 254708374149). |
| billRef | string | Required | The Account Number (for Paybill) or Invoice Number. |
| commandId | string | Optional | CustomerPayBillOnline (default) or CustomerBuyGoodsOnline. |
3. Gateway Callback Endpoints & Payload Schema
When you register your URLs, Safaricom posts real-time payment notifications directly to your Confirmation URL:
- Validation URL:
https://apidj.pgwiz.cloud/gateway/v1/c2b_validation.php - Confirmation URL:
https://apidj.pgwiz.cloud/gateway/v1/c2b_confirmation.php
Incoming C2B Callback Payload (from Safaricom)
| Field | Type | Description |
|---|---|---|
| TransID | string | M-PESA Receipt Number (e.g. UHQC348YHN). |
| TransAmount | string/numeric | Amount paid by customer (e.g. 1.00). |
| TransTime | string | Timestamp formatted as YYYYMMDDHHMMSS (e.g. 20260826103418). |
| BusinessShortCode | string | Receiving ShortCode / Till / Child ShortCode (e.g. 9579226). |
| BillRefNumber | string | Bill / Account reference number (for Paybill). |
| InvoiceNumber | string | Invoice number if applicable. |
| OrgAccountBalance | string/numeric | Organization account balance after the transaction settled (e.g. 6.00). |
| MSISDN | string | Customer Phone identifier (SHA-256 hashed in C2B v1; masked in C2B v2). |
| FirstName, LastName | string | Customer's registered M-PESA names (e.g. Peter). |
| TransactionType | string | Customer Merchant Payment (Buy Goods) or Pay Utility (Paybill). |
Example Callback Payload
{
"TransactionType": "Customer Merchant Payment",
"TransID": "UHQC348YHN",
"TransTime": "20260826103418",
"TransAmount": "1.00",
"BusinessShortCode": "9579226",
"BillRefNumber": "",
"InvoiceNumber": "",
"OrgAccountBalance": "6.00",
"ThirdPartyTransID": "",
"MSISDN": "ba776a5811b19bac7253658d7fc2bba1012a908ffe7928fff8364db3a093daa7",
"FirstName": "Peter"
}
For Buy Goods (Till numbers), Safaricom requires C2B URLs to be registered to the Child Shortcode (e.g. 9579226), which receives all payments made to the front-facing Till (e.g. 3188200) under Head Office (3534519).
Standing Order (M-PESA Ratiba)
Creates a recurring M-PESA payment instruction. The customer receives an NI push (STK prompt) to authorize the standing order. Once approved, M-PESA automatically debits the customer’s account at the specified frequency until the end date.
This endpoint is available through the API gateway. Ensure your M-PESA Ratiba product is enabled in the Daraja portal before use.
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
| standingOrderName | string | Required | A descriptive name for the standing order (e.g. “Monthly Subscription”). |
| startDate | string | Required | Start date in YYYYMMDD format. Must be a current or future date. |
| endDate | string | Required | End date in YYYYMMDD format. Must be after startDate. |
| businessShortCode | string | Required | The organization shortcode receiving the payments. |
| transactionType | string | Required | Type of standing order. One of: Standing Order Customer Pay Bill, Standing Order Customer Pay Merchant. |
| amount | integer | Required | Amount in KES to be debited each cycle. |
| partyA | string | Required | Customer phone number in 254XXXXXXXXX format. |
| callBackUrl | string | Required | URL where Safaricom will send transaction results. |
| frequency | string | Required | Payment frequency. Values: 1 (One-Off), 2 (Daily), 3 (Weekly), 4 (Monthly), 5 (Bi-Monthly), 6 (Quarterly), 7 (Half-Year), 8 (Yearly). |
| accountReference | string | Optional | Account reference for the payment. Default: "Standing Order". |
| transactionDesc | string | Optional | Human-readable description. Default: "M-PESA Standing Order". |
| receiverPartyIdentifierType | string | Optional | Receiver identifier type. 4 for shortcode (default). |
Example Request
{
"standingOrderName": "Monthly Subscription",
"startDate": "20260401",
"endDate": "20261231",
"businessShortCode": "174379",
"transactionType": "Standing Order Customer Pay Bill",
"amount": 500,
"partyA": "254712345678",
"callBackUrl": "https:///callback",
"frequency": "4",
"accountReference": "SUB-001",
"transactionDesc": "Monthly subscription payment"
}
Responses
{
"ResponseHeader": {
"responseCode": "0",
"responseDescription": "Success. Request accepted for processing."
}
}
{
"ResponseHeader": {
"responseCode": "400",
"responseDescription": "An internal error occurred."
},
"errorMessage": "Missing required parameter: 'partyA'"
}
Frequency Reference
| Value | Frequency |
|---|---|
| 1 | One-Off |
| 2 | Daily |
| 3 | Weekly |
| 4 | Monthly |
| 5 | Bi-Monthly (every 2 months) |
| 6 | Quarterly (every 3 months) |
| 7 | Half-Year (every 6 months) |
| 8 | Yearly |
Pull Transactions (C2B Reconciliation)
Query all C2B transactions performed under a shortcode within the last 48 hours. This is a two-step process: first register your shortcode for pull access (one-time), then query transactions within a date range.
This endpoint is available through the API gateway. Ensure your M-PESA Ratiba product is enabled in the Daraja portal before use.
Step 1: Register for Pull Access
A one-time registration to enable pulling transactions for your shortcode. This must be completed before you can query transactions.
Register — Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
| shortCode | string | Required | The organization shortcode to register for pull transactions. |
| nominatedNumber | string | Required | The MSISDN (phone number) nominated for pull access. |
| callBackUrl | string | Required | URL where Safaricom will send registration confirmation. |
Register — Example Request
{
"shortCode": "600000",
"nominatedNumber": "254712345678",
"callBackUrl": "https:///pull-callback"
}
Register — Responses
{
"ResponseCode": "0",
"ResponseMessage": "Success"
}
{
"ResponseStatus": "400",
"ResponseDescription": "An internal error occurred.",
"errorMessage": "Missing required parameter: 'shortCode'"
}
Step 2: Query Transactions
After registration, query C2B transactions within a date range. The date range must be within the last 48 hours. Results are paginated using an offset value.
Query — Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| shortCode | string | Required | The registered organization shortcode. |
| startDate | string | Required | Start of the query window in YYYY-MM-DD HH:MM:SS format. Must be within the last 48 hours. |
| endDate | string | Required | End of the query window in YYYY-MM-DD HH:MM:SS format. |
| offsetValue | string | Optional | Pagination offset for large result sets. Default: "0". |
Query — Example Request
{
"shortCode": "600000",
"startDate": "2026-03-02 00:00:00",
"endDate": "2026-03-04 23:59:59",
"offsetValue": "0"
}
Query — Responses
{
"ResponseCode": "1000",
"ResponseMessage": "Success",
"Response": [
{
"transactionId": "OEI2AK4Q16",
"trxDate": "2026-03-03 14:30:00",
"msisdn": "254712345678",
"sender": "John Doe",
"transactiontype": "Pay Bill",
"billreference": "INV-001",
"amount": "500.00",
"organizationname": "My Business"
}
]
}
{
"ResponseStatus": "400",
"ResponseDescription": "An internal error occurred.",
"errorMessage": "Date format must be YYYY-MM-DD HH:MM:SS"
}
Safaricom restricts pull transaction queries to a maximum window of 48 hours. If you need transactions older than 48 hours, use the Transaction Status endpoint to look up individual transactions by ID.
Code Examples
The following examples demonstrate how to call the STK Push endpoint. The same pattern applies to all other endpoints — change the URL and request body as needed.
curl -X POST https:///gateway/v1/stk_push.php \
-H "Content-Type: application/json" \
-H "X-Api-Key: your_api_key_here" \
-d '{
"phone": "254712345678",
"amount": 100,
"reference": "ORD-10042",
"description": "Payment for order #10042"
}'
<?php
$url = 'https:///gateway/v1/stk_push.php';
$payload = json_encode([
'phone' => '254712345678',
'amount' => 100,
'reference' => 'ORD-10042',
'description' => 'Payment for order #10042',
]);
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'X-Api-Key: your_api_key_here',
],
CURLOPT_POSTFIELDS => $payload,
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$data = json_decode($response, true);
if ($data['success']) {
echo 'CheckoutRequestID: ' . $data['data']['CheckoutRequestID'];
} else {
echo 'Error: ' . $data['error']['message'];
}
import requests
url = "https:///gateway/v1/stk_push.php"
headers = {
"Content-Type": "application/json",
"X-Api-Key": "your_api_key_here",
}
payload = {
"phone": "254712345678",
"amount": 100,
"reference": "ORD-10042",
"description": "Payment for order #10042",
}
response = requests.post(url, json=payload, headers=headers)
data = response.json()
if data["success"]:
print("CheckoutRequestID:", data["data"]["CheckoutRequestID"])
print("Remaining requests:", data["meta"]["rate_limit"]["remaining"])
else:
print("Error:", data["error"]["message"])
const url = "https:///gateway/v1/stk_push.php";
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Api-Key": "your_api_key_here",
},
body: JSON.stringify({
phone: "254712345678",
amount: 100,
reference: "ORD-10042",
description: "Payment for order #10042",
}),
});
const data = await response.json();
if (data.success) {
console.log("CheckoutRequestID:", data.data.CheckoutRequestID);
console.log("Remaining requests:", data.meta.rate_limit.remaining);
} else {
console.error("Error:", data.error.message);
}
Always check the success field before accessing data.
On error responses, the data field is absent and only
error is present.
Platform-Specific cURL Commands
If you're testing from your terminal, use the command appropriate for your platform. The main difference is quote escaping in the JSON payload.
curl -X POST \
-H "Content-Type: application/json" \
-H "X-Api-Key: your_api_key_here" \
-d '{
"phone": "254712345678",
"amount": 100,
"reference": "ORD-10042",
"description": "Payment for order #10042"
}'
curl.exe -X POST `
-H "Content-Type: application/json" `
-H "X-Api-Key: your_api_key_here" `
-d '{
"phone": "254712345678",
"amount": 100,
"reference": "ORD-10042",
"description": "Payment for order #10042"
}'
Note: PowerShell uses backticks (`) for line continuation. Use curl.exe to ensure you're using the system curl, not PowerShell's curl alias.
curl -X POST ^
-H "Content-Type: application/json" ^
-H "X-Api-Key: your_api_key_here" ^
-d "{\"phone\": \"254712345678\", \"amount\": 100, \"reference\": \"ORD-10042\", \"description\": \"Payment for order #10042\"}"
Note: CMD uses caret (^) for line continuation and requires escaped quotes in JSON (\"). For easier JSON handling, save the payload to a file and use -d @file.json.
Error Reference
All errors return a consistent JSON envelope with an HTTP status code and a descriptive message. Below is the complete list of error codes you may encounter.
| Code | Status | Description | Common Causes |
|---|---|---|---|
| 400 | Bad Request | The request body is missing a required parameter or contains invalid data. |
Missing phone or amount. Amount less than 1.
Invalid commandId value. Malformed JSON body.
|
| 401 | Unauthorized | The API key is missing, invalid, or has been revoked. |
No X-Api-Key or Authorization header provided.
Key was deleted or deactivated in the dashboard.
|
| 403 | Forbidden | The API key is valid but the associated account is suspended or deleted. | Account was suspended by an administrator. Account was soft-deleted. |
| 405 | Method Not Allowed | The request used an HTTP method other than POST. |
Sending a GET, PUT, or DELETE request
instead of POST.
|
| 429 | Too Many Requests | You have exceeded your daily rate limit for your tier. |
Free tier exceeded 50 requests. Basic tier exceeded 500 requests.
Check X-RateLimit-Reset header for when the window resets.
|
| 502 | Bad Gateway | The upstream Safaricom M-PESA API returned an error or is unreachable. | Safaricom Daraja API is down or experiencing issues. Invalid M-PESA configuration on the server. OAuth token generation failed. |
Error response format
Every error response uses the same JSON structure regardless of the status code:
{
"success": false,
"error": {
"code": 401,
"message": "Invalid or revoked API key."
}
}
Handling errors
- 400 — Fix the request body. Check required parameters and data types.
- 401 — Verify your API key. Generate a new one if needed from /user/.
- 403 — Contact the administrator to reactivate your account.
- 405 — Ensure you are sending a
POSTrequest. - 429 — Wait until the reset time or upgrade your tier.
- 502 — Retry after a short delay. If persistent, check M-PESA service status.
Try It — STK Push
Test the STK Push endpoint directly from this page. Enter your API key and the required parameters below. This sends a real request to the gateway.
This sends a live request to the M-PESA gateway. If the server is configured with production credentials, the customer will receive an actual STK Push prompt on their phone. Use with caution.