MyPay

MyPay API

MyPay lets you collect M-Pesa payments straight into your till, paybill or bank account with a single API call. You don't need your own Daraja app. We send the STK push, Safaricom tells us the result, and we forward it to your callback URL.

Base URL
https://mypay.co.ke/api/v1
Format
JSON request bodies (Content-Type: application/json) or form data. JSON responses.
Method
All endpoints use POST.

How a payment works

  1. Your system calls STK Push with the amount, phone number and reference.
  2. The customer gets an M-Pesa prompt on their phone and enters their PIN.
  3. Money goes directly to your till, paybill or bank account.
  4. MyPay POSTs the result to the account's callback URL. You can also poll the status.

Account types

One MyPay login can link many accounts. Each account has its own API key, signin key, callback URL, payment pages and subscription.

TypeMoney goes toM-Pesa transaction typeAccount reference used
TillYour till numberCustomerBuyGoodsOnlineThe reference you send
PaybillYour paybill numberCustomerPayBillOnlineaccount_number if you send it, otherwise reference
Bank accountYour bank's paybillCustomerPayBillOnlineAlways your bank account number. It is fixed when you link the account, and any reference or account number in the request is ignored.

Whatever the account type, the reference you send is stored and returned to you as TransactionReference in callbacks and status responses, so you can match payments to orders.

Activation & billing

Every linked account costs KES 350 per month. New accounts are inactive. While an account is inactive or expired:

  • STK Push requests are rejected with MP402 (never paid) or MP403 (expired).
  • Its payment pages show "not accepting payments".

To activate, open the account in your dashboard and click Pay & activate. You'll get an M-Pesa prompt to pay paybill 4322685 (account MYPAY<account id>). The account activates automatically when payment is confirmed. You can pay for 1, 3, 6 or 12 months at once, and renewing early adds the time on top of your current expiry date.

Authentication

Each request includes the account's api_key and signin_key in the request body. You'll find them on the account page in your dashboard.

The signin key is shown only once, when the account is created or the keys are regenerated. Store it safely on your server and never put it in a browser or mobile app. If you lose it, regenerate the keys. The old keys stop working immediately.

STK Push

Sends an M-Pesa payment prompt to the customer's phone.

POSThttps://mypay.co.ke/api/v1/stkpush

Request

FieldTypeDescription
api_keystringRequiredYour API key
signin_keystringRequiredYour signin key
amountstringRequiredAmount in KES, whole number (minimum 1)
msisdnstringRequiredCustomer's phone number, e.g. 0712345678 or 254712345678
referencestringRequiredPayment reference / description, e.g. your order number. Returned to you as TransactionReference.
account_numberstringOptionalAccount number for Paybill accounts. If you leave it out, reference is used. Ignored for till and bank accounts.

Example: cURL

curl -X POST https://mypay.co.ke/api/v1/stkpush \
  -H "Content-Type: application/json" \
  -d '{
    "api_key": "mp_live_xxxxxxxxxxxxxxxx",
    "signin_key": "mpsk_xxxxxxxxxxxxxxxxxxxxxxxx",
    "amount": "100",
    "msisdn": "0712345678",
    "reference": "ORDER-001"
  }'

Example: PHP

$payload = [
    'api_key'    => 'mp_live_xxxxxxxxxxxxxxxx',
    'signin_key' => 'mpsk_xxxxxxxxxxxxxxxxxxxxxxxx',
    'amount'     => '100',
    'msisdn'     => '0712345678',
    'reference'  => 'ORDER-001',
    // 'account_number' => 'INV-2045', // paybill accounts only
];

$ch = curl_init('https://mypay.co.ke/api/v1/stkpush');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_POSTFIELDS     => json_encode($payload),
    CURLOPT_HTTPHEADER     => ['Content-Type: application/json'],
    CURLOPT_RETURNTRANSFER => true,
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

if ($response['success']) {
    // Save this to match the callback later
    $transactionRequestId = $response['transaction_request_id'];
} else {
    echo $response['error_code'] . ': ' . $response['message'];
}

Example: JavaScript (Node.js)

const res = await fetch('https://mypay.co.ke/api/v1/stkpush', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    api_key: process.env.MYPAY_API_KEY,
    signin_key: process.env.MYPAY_SIGNIN_KEY,
    amount: '100',
    msisdn: '0712345678',
    reference: 'ORDER-001',
  }),
});
const data = await res.json();

Example: Python

import requests

r = requests.post("https://mypay.co.ke/api/v1/stkpush", json={
    "api_key": "mp_live_xxxxxxxxxxxxxxxx",
    "signin_key": "mpsk_xxxxxxxxxxxxxxxxxxxxxxxx",
    "amount": "100",
    "msisdn": "0712345678",
    "reference": "ORDER-001",
})
print(r.json())

Success response HTTP 200

{
  "success": true,
  "ResponseCode": 0,
  "message": "STK push sent. Customer should enter their M-Pesa PIN.",
  "transaction_request_id": "MPY20261010153045123456",
  "MerchantRequestID": "29115-34620561-1",
  "CheckoutRequestID": "ws_CO_123456789",
  "amount": 100,
  "msisdn": "254712345678",
  "reference": "ORDER-001"
}

A success response only means the prompt was sent. Wait for the callback or check the status to know whether the customer paid. Save transaction_request_id, because the callback includes it as TransactionID.

Error response HTTP 4xx / 5xx

{
  "success": false,
  "error_code": "MP402",
  "message": "Account is inactive. Pay the KES 350 monthly subscription to activate it."
}

See all error codes.

Transaction status

Gets the current status of a payment. If a payment is still pending after 30 seconds, MyPay checks with Safaricom directly before it responds.

POSThttps://mypay.co.ke/api/v1/status

Request

FieldTypeDescription
api_keystringRequiredYour API key
signin_keystringRequiredYour signin key
transaction_request_idstringRequiredTransaction ID from the STK Push response
curl -X POST https://mypay.co.ke/api/v1/status \
  -H "Content-Type: application/json" \
  -d '{
    "api_key": "mp_live_xxxxxxxxxxxxxxxx",
    "signin_key": "mpsk_xxxxxxxxxxxxxxxxxxxxxxxx",
    "transaction_request_id": "MPY20261010153045123456"
  }'

Response

{
  "success": true,
  "status": "success",
  "ResponseCode": 0,
  "ResponseDescription": "Success",
  "MerchantRequestID": "29115-34620561-1",
  "CheckoutRequestID": "ws_CO_123456789",
  "TransactionID": "MPY20261010153045123456",
  "TransactionAmount": 100,
  "TransactionReceipt": "SHJ7ABCDEF",
  "TransactionDate": "20261010153045",
  "TransactionReference": "ORDER-001",
  "Msisdn": "254712345678"
}

status is one of pending, success or failed. While a payment is pending, ResponseCode is null.

Callbacks (webhooks)

Add a callback URL to each account in your dashboard. Each account must use its own, unique callback URL. When a payment succeeds or fails, MyPay sends a POST with a JSON body to that URL.

Successful payment

{
  "ResponseCode": 0,
  "ResponseDescription": "Success",
  "MerchantRequestID": "29115-34620561-1",
  "CheckoutRequestID": "ws_CO_123456789",
  "TransactionID": "MPY20261010153045123456",
  "TransactionAmount": 100,
  "TransactionReceipt": "SHJ7ABCDEF",
  "TransactionDate": "20261010153045",
  "TransactionReference": "ORDER-001",
  "Msisdn": "254712345678"
}

Failed or cancelled payment

{
  "ResponseCode": 1032,
  "ResponseDescription": "Request cancelled by user",
  "MerchantRequestID": "29115-34620561-1",
  "CheckoutRequestID": "ws_CO_123456789",
  "TransactionID": "MPY20261010153045123456",
  "TransactionAmount": 100,
  "TransactionReceipt": null,
  "TransactionDate": null,
  "TransactionReference": "ORDER-001",
  "Msisdn": "254712345678"
}

ResponseCode 0 means paid. Any other value is an M-Pesa result code explaining why the payment failed.

Headers

HeaderDescription
X-MyPay-Eventpayment.success, payment.failed or test
X-MyPay-SignatureHMAC-SHA256 of the raw request body, using the account's webhook secret as the key (hex encoded)

Delivery & retries

  • Respond with any 2xx status within 15 seconds to acknowledge.
  • If your endpoint fails, MyPay retries up to 6 times, waiting longer before each retry (1, 2, 4, 8 and 16 minutes).
  • You can resend any callback from the dashboard. Use Send test callback to try out your handler.
  • The same payment can arrive more than once, so check TransactionID and skip payments you've already processed.

Webhook handler sample

<?php
// webhook.php - Handle MyPay webhook notifications

$json = file_get_contents('php://input');

// Optional but recommended: verify the signature
$secret = 'whsec_xxxxxxxxxxxxxxxx'; // account webhook secret from the dashboard
$expected = hash_hmac('sha256', $json, $secret);
if (!hash_equals($expected, $_SERVER['HTTP_X_MYPAY_SIGNATURE'] ?? '')) {
    http_response_code(401);
    exit('Invalid signature');
}

$data = json_decode($json, true);

// Validate webhook data
if (!$data || !isset($data['TransactionID'])) {
    http_response_code(400);
    echo "Invalid webhook data";
    exit;
}

$responseCode  = $data['ResponseCode'];
$transactionId = $data['TransactionID'];
$amount        = $data['TransactionAmount'];
$receipt       = $data['TransactionReceipt'];
$phone         = $data['Msisdn'];
$reference     = $data['TransactionReference'];

if ($responseCode == 0) {
    // Payment successful - update your database
    processSuccessfulPayment($transactionId, $amount, $receipt, $phone, $reference);
    http_response_code(200);
    echo json_encode(['status' => 'success']);
} else {
    // Payment failed
    processFailedPayment($transactionId, $responseCode);
    http_response_code(200);
    echo json_encode(['status' => 'received']);
}

Payment pages

Don't have a website? Create a payment page for any account in the dashboard and share its link, such as https://mypay.co.ke/pay/mama-mboga. Customers don't need to log in. They enter their phone number (and the amount, if it isn't fixed) and get an M-Pesa prompt.

  • Fixed amount: every customer pays the same amount, e.g. a KES 500 ticket.
  • Customer enters amount: you can set a minimum amount.
  • Till and paybill pages can let the customer type their own reference or account number, e.g. a house number.
  • Bank pages always pay to your linked bank account number.
  • Page payments are sent to the account's callback URL like any API payment.

MyPay error codes

CodeHTTPMeaning
MP400400Invalid request body. Send JSON or form data.
MP401401Missing or invalid api_key / signin_key.
MP402402Account is inactive. Pay the KES 350 monthly subscription to activate it.
MP403403Account subscription has expired. Renew to continue receiving payments.
MP404404Transaction not found for this account.
MP405405Method not allowed. Use POST.
MP410403Account is suspended. Contact MyPay support.
MP420422Invalid amount. It must be a whole number of at least 1.
MP421422Invalid msisdn. Use 07XXXXXXXX, 01XXXXXXXX or 2547XXXXXXXX.
MP422422Reference is required.
MP423422transaction_request_id is required.
MP424422Account number is too long (max 40 characters).
MP500500Internal server error. Try again.
MP502502Could not reach Safaricom. Try again shortly.
MP503502Safaricom rejected the STK push request.
MP429429Too many payment requests for this phone number (payment pages). Wait a minute.

M-Pesa result codes

These come from Safaricom and are passed through as ResponseCode in callbacks and status responses.

CodeMeaning
0Success. The customer paid.
1Insufficient M-Pesa balance
1001Another transaction is already in progress for this customer
1019Transaction expired
1025 / 9999Error sending the push request
1032Cancelled by the customer
1037Customer's phone could not be reached (offline or timed out)
2001Wrong M-Pesa PIN