Contents

1. Introduction

2. Setup — Bring Your Own Shortcode

3. Initiate STK Push

4. Payment Webhook Callback

5. Poll Payment Status

6. Dynamic Payment Links

Sozuri Payments (Zuka) lets your project collect M-Pesa payments via STK Push using your own registered paybill or till number. Bring your Daraja API credentials, plug them in once, and Sozuri handles authentication, callbacks, SMS notifications, and webhook forwarding automatically.

The base URL for all Payments API calls is: https://sozuri.net/api/v1/zuka/push

Sozuri Payments Dashboard

Register your M-Pesa credentials in your project's Payments settings. You need a Daraja API application at developer.safaricom.co.ke.

Field Required for STK Description
Business Short Code Yes Your M-Pesa paybill or till number
Consumer Key Yes From your Daraja API application
Consumer Secret Yes From your Daraja API application
Passkey Yes From your Daraja STK Push setup
Webhook URL Optional Your endpoint to receive confirmed payment notifications
Notification Numbers Optional Comma-separated numbers (cashier, manager) that receive SMS alerts
Payments Settings

This sends an M-Pesa payment prompt to your customer's phone. The customer enters their PIN and Sozuri handles the rest — recording the transaction, sending SMS notifications, and forwarding to your webhook.

Parameter Required Type Description
phone Yes String Customer's Kenyan phone number in any format (07xx, 254xx, +254xx)
amount Yes Integer Amount in KES, minimum 1
account_reference No String Max 12 chars. Invoice number, student ID, etc. Defaults to project code.

POST Request — Initiate STK Push

POST /api/v1/zuka/push HTTP/1.1
Host: sozuri.net
Authorization: Bearer YOUR_PROJECT_API_KEY
Content-Type: application/json
Accept: application/json

{
  "phone": "0712345678",
  "amount": 500,
  "account_reference": "INV-001"
}
<?php

$curl = curl_init();

curl_setopt_array($curl, [
    CURLOPT_URL            => 'https://sozuri.net/api/v1/zuka/push',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_POSTFIELDS     => json_encode([
        'phone'             => '0712345678',
        'amount'            => 500,
        'account_reference' => 'INV-001',
    ]),
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer YOUR_PROJECT_API_KEY',
        'Content-Type: application/json',
        'Accept: application/json',
    ],
]);

$response = curl_exec($curl);
curl_close($curl);

$result = json_decode($response, true);
echo $result['checkout_request_id'];
// JavaScript — Fetch
const response = await fetch('https://sozuri.net/api/v1/zuka/push', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer YOUR_PROJECT_API_KEY',
    'Content-Type': 'application/json',
    'Accept': 'application/json',
  },
  body: JSON.stringify({
    phone: '0712345678',
    amount: 500,
    account_reference: 'INV-001',
  }),
});

const data = await response.json();
console.log(data.checkout_request_id);
import http.client, json

conn = http.client.HTTPSConnection("sozuri.net")
payload = json.dumps({
    "phone": "0712345678",
    "amount": 500,
    "account_reference": "INV-001"
})
headers = {
    "Authorization": "Bearer YOUR_PROJECT_API_KEY",
    "Content-Type": "application/json",
    "Accept": "application/json"
}
conn.request("POST", "/api/v1/zuka/push", payload, headers)
res = conn.getresponse()
print(res.read().decode("utf-8"))
curl --request POST 'https://sozuri.net/api/v1/zuka/push' \
  --header 'Authorization: Bearer YOUR_PROJECT_API_KEY' \
  --header 'Content-Type: application/json' \
  --header 'Accept: application/json' \
  --data '{
    "phone": "0712345678",
    "amount": 500,
    "account_reference": "INV-001"
  }'

Success Response (200)

{
  "message": "STK push sent. Prompt your customer to check their phone.",
  "checkout_request_id": "ws_CO_22042026115226183725164293",
  "merchant_request_id": "7ddc-4cfc-bb79-a1658fc680ae20423067"
}

Error Response (422)

{
  "error": "No active paybill with STK credentials found for this project."
}

When a payment is confirmed, Sozuri POSTs to your webhook URL (set in Payments settings). The payload mirrors the M-Pesa C2B structure for easy parsing.

Sozuri also sends SMS notifications to the customer and all numbers in numbers_to_notify. Each SMS includes a shared OTP code — the cashier can verify the customer paid by asking for this code, preventing fake screenshot fraud.

Webhook POST Body

{
  "TransactionType": "Pay Bill",
  "TransID": "QGH7JKLM2P",
  "TransTime": "20260422115226",
  "TransAmount": "500.00",
  "BusinessShortCode": "123456",
  "BillRefNumber": "INV-001",
  "InvoiceNumber": "",
  "OrgAccountBalance": "",
  "ThirdPartyTransID": "",
  "MSISDN": "254712345678",
  "FirstName": "John",
  "MiddleName": "",
  "LastName": "Doe"
}
Payment Confirmed Transaction

After initiating an STK push, poll this endpoint to check if the customer has completed payment.

Poll every 5 seconds. Stop after 24 attempts (2 minutes) or when status is confirmed or fail.

Status Meaning
pendingWaiting for customer to enter PIN or M-Pesa to callback
confirmedPayment successful — trans_id and amount are populated
failCustomer cancelled, timeout, or insufficient funds

GET /zuka/pay/{code}/status/{checkout_request_id}

curl https://sozuri.net/zuka/pay/YOUR_PAYBILL_CODE/status/ws_CO_22042026115226183725164293

Response

// Pending
{ "status": "pending", "trans_id": null, "amount": null }

// Confirmed
{ "status": "confirmed", "trans_id": "QGH7JKLM2P", "amount": "500.00" }

// Failed
{ "status": "fail", "trans_id": null, "amount": null }

Every configured paybill automatically gets a hosted payment page that anyone can open and pay from — no code required.

https://sozuri.net/zuka/pay/{your-paybill-code}

The customer opens the link, enters their phone number and amount, and receives an STK Push. The page polls for confirmation and shows a live payment status.

Use cases: Share in WhatsApp / email • Print as QR code on receipts • Embed in a school portal • Event ticket payments • Donation pages

Payment Link Page

Find your payment link in your project's Payments dashboard. Copy it or click to test.