Comprehensive documentation for integrating with our payment system
The Shadow Payment Gateway API allows you to integrate M-Pesa STK Push payments into your applications. This documentation provides details on how to configure your system, initiate payments, and track transactions in real-time.
All API endpoints are relative to the base URL:
All API requests require authentication using API keys. Include these in the request headers:
X-API-Key: Your_API_KeyX-API-Secret: Your_API_SecretContent-Type: application/jsonAll API responses are returned in JSON format with a consistent structure:
{
"success": true|false,
"message": "Descriptive message",
// Additional data fields depending on the endpoint
}
Use this interface to test the Shadow Pay API endpoints with your credentials.
To use the Shadow API, you need to:
Here's a basic PHP implementation to get you started:
<?php
// Initialize payment
function initiatePayment($apiKey, $apiSecret, $paymentAccountId, $phone, $amount, $reference, $description) {
$url = "https://shadow-pay.top/api/v2/stkpush.php";
$data = [
'payment_account_id' => $paymentAccountId,
'phone' => $phone,
'amount' => $amount,
'reference' => $reference,
'description' => $description
];
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'X-API-Key: ' . $apiKey,
'X-API-Secret: ' . $apiSecret,
'Content-Type: application/json'
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return json_decode($response, true);
}
// Your API credentials
$apiKey = "your_api_key_here";
$apiSecret = "your_api_secret_here";
$paymentAccountId = 14; // Your payment account ID
// Example usage
$result = initiatePayment($apiKey, $apiSecret, $paymentAccountId, "254712345678", 100, "ORDER123", "Test payment");
if ($result['success']) {
echo "STK push sent successfully. Checkout Request ID: " . $result['checkout_request_id'];
} else {
echo "Error: " . $result['message'];
}
?>
This endpoint initiates an M-Pesa STK Push payment request.
| Parameter | Type | Required | Description |
|---|---|---|---|
| payment_account_id | Integer | Yes | Your payment account ID |
| phone | String | Yes | Customer phone number (format: 254712345678) |
| amount | Float | Yes | Payment amount (minimum 1 KES) |
| reference | String | No | Your internal reference for the transaction |
| description | String | No | Description of the payment |
{
"payment_account_id": 17,
"phone": "254712345678",
"amount": 100,
"reference": "ORDER_12345",
"description": "Payment for order #12345"
}
{
"success": true,
"message": "STK push sent successfully",
"checkout_request_id": "ws_CO_20230101120000_abc123def456",
"merchant_request_id": "ws_MR_20230101120000_xyz789uvw012"
}
{
"success": false,
"message": "Error description"
}
This endpoint checks the status of a payment transaction.
| Parameter | Type | Required | Description |
|---|---|---|---|
| checkout_request_id | String | Yes | The checkout request ID from the STK Push response |
{
"checkout_request_id": "ws_CO_20230101120000_abc123def456"
}
{
"success": true,
"status": "completed",
"amount": 100,
"phone": "254712345678",
"transaction_code": "ABC123DEF456",
"created_at": "2023-01-01 12:00:00"
}
This endpoint retrieves a list of your transactions with pagination support.
| Parameter | Type | Required | Description |
|---|---|---|---|
| page | Integer | No | Page number (default: 1) |
| limit | Integer | No | Number of items per page (max: 100, default: 10) |
{
"success": true,
"data": [
{
"id": 123,
"amount": 100,
"status": "completed",
"phone": "254712345678",
"transaction_code": "ABC123DEF456",
"fee_amount": 1.5,
"fee_deducted": true,
"type": "API",
"created_at": "2023-01-01 12:00:00",
"completed_at": "2023-01-01 12:02:30"
}
],
"pagination": {
"current_page": 2,
"per_page": 20,
"total_items": 150,
"total_pages": 8
}
}
To track transactions in real-time, implement a polling mechanism that checks the status of a payment at regular intervals until it's completed or failed.
<?php
// Function to check payment status
function checkPaymentStatus($apiKey, $apiSecret, $checkoutRequestId) {
$url = "https://shadow-pay.top/api/v2/status.php";
$data = ['checkout_request_id' => $checkoutRequestId];
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'X-API-Key: ' . $apiKey,
'X-API-Secret: . $apiSecret,
'Content-Type: application/json'
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return json_decode($response, true);
}
// Example usage after initiating payment
$checkoutRequestId = $result['checkout_request_id']; // From STK Push response
// Check status with polling (every 5 seconds for up to 2 minutes)
$maxAttempts = 24;
$attempt = 0;
while ($attempt < $maxAttempts) {
$status = checkPaymentStatus($apiKey, $apiSecret, $checkoutRequestId);
if ($status['success']) {
if ($status['status'] === 'completed') {
echo "Payment completed. Transaction Code: " . $status['transaction_code'];
break;
} elseif ($status['status'] === 'failed') {
echo "Payment failed.";
break;
}
// If still pending, continue polling
}
$attempt++;
sleep(5); // Wait 5 seconds before next check
}
if ($attempt >= $maxAttempts) {
echo "Payment status check timeout.";
}
?>
The API uses standard HTTP status codes to indicate success or failure:
| Code | Description |
|---|---|
| 200 | Success |
| 400 | Bad Request - Invalid parameters |
| 401 | Unauthorized - Invalid API credentials |
| 404 | Not Found - Resource not found |
| 405 | Method Not Allowed |
| 500 | Internal Server Error |
Insufficient service balance - Top up your account to cover transaction feesInvalid payment account ID - Check your payment account IDTransaction not found - The checkout_request_id doesn't existMissing required parameter - Check that all required parameters are providedHere's a complete PHP implementation that includes a user interface for testing the API:
<?php
// =========================
// Shadow STK Push Demo
// =========================
// - Users enter phone + amount in a form
// - System sends STK Push using Shadow API
// - Tracks payment automatically in real-time
// =========================
// ==== API CREDENTIALS ====
// (replace these with your real Shadow keys)
$apiKey = "7390a54c44bcb9cb692f6c861562ff6f3b424094bef993d3434e692b17e02c46";
$apiSecret = "882e5a38596b0fd6cfd8f9631592e4290ca4f441a2be5990ec34d778974985c4";
$accountId = 17;
// ==== FUNCTIONS ====
// Function to send STK Push
function initiatePayment($apiKey, $apiSecret, $accountId, $phone, $amount, $reference, $description) {
$url = "https://shadow-pay.top/api/v2/stkpush.php";
$data = [
'payment_account_id' => $accountId,
'phone' => $phone,
'amount' => $amount,
'reference' => $reference,
'description' => $description
];
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'X-API-Key: ' . $apiKey,
'X-API-Secret: ' . $apiSecret,
'Content-Type: application/json'
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
$response = curl_exec($ch);
curl_close($ch);
return json_decode($response, true);
}
// Function to check payment status
function checkPaymentStatus($apiKey, $apiSecret, $checkoutRequestId) {
$url = "https://shadow-pay.top/api/v2/status.php";
$data = ['checkout_request_id' => $checkoutRequestId];
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'X-API-Key: ' . $apiKey,
'X-API-Secret: ' . $apiSecret,
'Content-Type: application/json'
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
$response = curl_exec($ch);
curl_close($ch);
return json_decode($response, true);
}
?>
<!DOCTYPE html>
<html>
<head>
<title>Shadow STK Push Demo</title>
<style>
body { font-family: Arial, sans-serif; background:#f4f4f4; }
.container { max-width:500px; margin:50px auto; background:#fff; padding:20px; border-radius:10px; box-shadow:0 0 10px rgba(0,0,0,0.1); }
h2 { text-align:center; color:#333; }
label { font-weight:bold; display:block; margin-top:10px; }
input[type="text"], input[type="number"] { width:100%; padding:10px; margin-top:5px; border:1px solid #ccc; border-radius:5px; }
button { margin-top:15px; padding:12px; background:#28a745; color:white; border:none; border-radius:5px; width:100%; font-size:16px; cursor:pointer; }
button:hover { background:#218838; }
.status-box { margin-top:20px; padding:15px; border-radius:8px; background:#f9f9f9; font-family:monospace; }
.success { color:green; }
.error { color:red; }
.pending { color:orange; }
</style>
</head>
<body>
<div class="container">
<h2>💳 Shadow STK Push</h2>
<form method="POST">
<label>Phone Number (format: 2547XXXXXXXX)</label>
<input type="text" name="phone" required placeholder="e.g. 254712345678">
<label>Amount (KES)</label>
<input type="number" name="amount" required placeholder="e.g. 100">
<button type="submit" name="pay">Send STK Push</button>
</form>
<?php
// ==== WHEN USER SUBMITS FORM ====
if (isset($_POST['pay'])) {
$phone = $_POST['phone'];
$amount = $_POST['amount'];
$reference = "ORDER" . rand(1000,9999); // unique ref
$description = "Payment via Shadow API";
echo "<div class='status-box'>";
echo "<p>📡 Sending STK Push to <b>$phone</b> for <b>KES $amount</b>...</p>";
$result = initiatePayment($apiKey, $apiSecret, $accountId, $phone, $amount, $reference, $description);
if (!$result['success']) {
echo "<p class='error'>❌ Error: " . $result['message'] . "</p>";
echo "</div>";
} else {
$checkoutRequestId = $result['checkout_request_id'];
echo "<p class='success'>✅ STK Push sent successfully!</p>";
echo "<p>Checkout Request ID: <b>$checkoutRequestId</b></p>";
echo "<hr><p>🔍 Tracking payment status...</p>";
// Auto-poll status
$maxAttempts = 24; // 2 minutes
$attempt = 0;
while ($attempt < $maxAttempts) {
$status = checkPaymentStatus($apiKey, $apiSecret, $checkoutRequestId);
if ($status['success']) {
$paymentStatus = $status['status'];
echo "<p class='pending'>⏳ Attempt " . ($attempt + 1) . ": Status = <b>$paymentStatus</b></p>";
flush();
if ($paymentStatus === "completed") {
echo "<p class='success'>🎉 Payment Completed!</p>";
echo "<p>Transaction Code: <b>" . $status['transaction_code'] . "</b></p>";
break;
} elseif ($paymentStatus === "failed") {
echo "<p class='error'>❌ Payment Failed.</p>";
break;
}
} else {
echo "<p class='error'>⚠️ API Error: " . $status['message'] . "</p>";
break;
}
$attempt++;
sleep(5);
}
if ($attempt >= $maxAttempts) {
echo "<p class='error'>⌛ Payment status check timed out.</p>";
}
echo "</div>";
}
}
?>
</div>
</body>
</html>
If you need help integrating with our API, please contact our support team: