Payments API — Sozuri Zuka
Contents
2. Setup — Bring Your Own Shortcode
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
Setup — Bring Your Own Shortcode (BYOS)
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 |
Initiate STK Push
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."
}
Payment Webhook Callback
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"
}
Poll Payment Status
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 |
|---|---|
| pending | Waiting for customer to enter PIN or M-Pesa to callback |
| confirmed | Payment successful — trans_id and amount are populated |
| fail | Customer 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 }
Dynamic Payment Links
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
Find your payment link in your project's Payments dashboard. Copy it or click to test.