The 2026 Marketing Planning Guide: Why Strategy Will Always Beat Tactics
January 14, 2026Are you looking for a simple and reliable way to integrate MPESA STK Push into your PHP website? MPESA is Kenya’s leading mobile payment platform, trusted by millions of users and businesses daily. Whether you run an e-commerce store, school management system, SACCO portal, booking platform, or subscription-based website, integrating MPESA payments can significantly improve customer experience and increase successful transactions. Using Safaricom’s Daraja API, developers can initiate payment requests directly to customers’ phones, receive real-time transaction updates, and verify payment status automatically.
In this tutorial, you will learn how to:
- MPESA STK Push Integration in PHP
- Set up a Safaricom Daraja account
- Create a Daraja Sandbox application
- Generate access tokens
- Initiate STK Push requests
- Handle callback responses
- Verify transaction status
- Store payment records in MySQL
By the end of this guide, you’ll have a fully functional MPESA payment gateway integrated into your PHP application.
What Is MPESA STK Push?
STK Push (SIM Tool Kit Push) is a payment request sent directly to a customer’s mobile phone. Instead of manually entering a Paybill number and account reference, the customer receives a prompt on their phone requesting them to enter their MPESA PIN to complete payment.
This provides a faster, more secure, and user-friendly payment experience.
How MPESA STK Push Works
- Customer enters their phone number on your website.
- Your website sends a request to Safaricom Daraja API.
- Safaricom sends a payment prompt to the customer’s phone.
- Customer enters their MPESA PIN.
- Payment is processed.
- Safaricom sends a callback response to your server.
- Your website updates the transaction status automatically.
Types of MPESA API Integrations
Safaricom offers several integration services through the Daraja API.
Business-to-Customer (B2C)
Allows businesses to send money directly to customers.
Examples:
- Salary payments
- Loan disbursements
- Refunds
Business-to-Business (B2B)
Enables businesses to transfer funds between Paybill and Till Numbers.
Examples:
- Supplier payments
- Partner settlements
Customer-to-Business (C2B)
Allows customers to make payments to businesses. (See integration here)
Examples:
- School fees
- Utility payments
- Membership subscriptions
Lipa na MPESA Online (STK Push)
Allows merchants to initiate payments on behalf of customers through a mobile phone prompt.
This is the integration method we will implement in this tutorial.
Prerequisites
Before proceeding, ensure you have:
- PHP 7.4 or higher
- MySQL Database
- cURL enabled on your server
- Safaricom Daraja Developer Account
- Basic PHP knowledge
- Web server (Apache or Nginx)
Step 1: Create a Safaricom Daraja Developer Account
Visit the Daraja Safaricom portal and create a developer account.
Once registered:
- Log in to the dashboard.
- Click Add New App.
- Enter your application name.
- Select:
- MPESA Sandbox
- Lipa na MPESA Sandbox
- Click Create App.
After successful creation, Safaricom will provide:
- Consumer Key
- Consumer Secret
Keep these credentials secure because they are required for API authentication. Learn more here
Step 2: Set Up Your Project Structure
Create the following folder structure:
mpesa/ │ ├── checkout.php ├── express-stk.php ├── callback.php ├── confirm-payment.php ├── status.php ├── transaction_log └── database.sql
Purpose of Each File
| File | Purpose |
|---|---|
| checkout.php | Checkout page where users enter their MPESA number |
| express-stk.php | Initiates the STK Push request |
| callback.php | Receives and processes Safaricom callback responses |
| confirm-payment.php | Confirms payment status to the customer |
| status.php | Performs transaction status queries |
| transaction_log | Logs callback responses and API activity |
| database.sql | Database structure for storing transactions |
Step 3: Create the Database
Create a file called database.sql.
CREATE DATABASE mpesa; USE mpesa; CREATE TABLE orders ( ID INT AUTO_INCREMENT PRIMARY KEY, OrderNo VARCHAR(100), Amount DECIMAL(10,2), Phone VARCHAR(20), MerchantRequestID VARCHAR(255), CheckoutRequestID VARCHAR(255), MpesaCode VARCHAR(50), TransactionDate VARCHAR(50), Status VARCHAR(50) DEFAULT 'PENDING', CreatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP );
Import this file into MySQL.
Step 4: Create the Checkout Page
The checkout page collects the customer’s phone number and initiates the payment process.
The customer should:
- Enter their MPESA number.
- Click Confirm and Pay.
- Receive a payment prompt on their phone.
- Enter their MPESA PIN.
<?php include('express-stk.php'); ?>
<!DOCTYPE html> <html>
<head> <title>Pay with MPESA</title> </head>
<body>
<h2>MPESA Payment</h2>
<form method="POST">
<input type="hidden" name="orderNo" value="ORD-<?php echo time();?>">
<input type="text" name="phone_number" placeholder="0700000000" required>
<button type="submit"> Confirm and Pay </button>
</form>
<?php if(!empty($errmsg)){ echo "<p>".$errmsg."</p>"; } ?>
</body>
</html>
The form submits the phone number to express-stk.php, which handles the API request.
Important Validation Tips
Always validate:
- Empty phone numbers
- Invalid formats
- Duplicate submissions
Convert phone numbers into the correct format:0700123456 to:254700123456before sending requests to Safaricom.
Step 5: Create the STK Push Request
The next step is generating an access token and sending an STK Push request to Safaricom.
Configuration Settings
Your configuration should include:
$config = [
"env" => "sandbox",
"BusinessShortCode" => "174379",
"key" => "YOUR_CONSUMER_KEY",
"secret" => "YOUR_CONSUMER_SECRET",
"passkey" => "YOUR_PASSKEY",
"CallBackURL" => "https://yourdomain.com/callback.php"
];
Generate Access Token
Daraja API requires OAuth authentication.
The access token is generated using:
- Consumer Key
- Consumer Secret
The token is then used to authenticate all subsequent API requests.
Create STK Password
Safaricom requires a Base64 encoded password generated from:
BusinessShortCode + Passkey + Timestamp
This password secures the payment request.
Send STK Push Request
The API request includes:
- Amount
- Phone Number
- Business Short Code
- Callback URL
- Account Reference
- Transaction Description
<?php session_start(); $errmsg=''; $config=[ "env"=>"sandbox", "BusinessShortCode"=>"174379", "key"=>"YOUR_CONSUMER_KEY", "secret"=>"YOUR_CONSUMER_SECRET", "passkey"=>"YOUR_PASSKEY", "TransactionType"=>"CustomerPayBillOnline", "CallBackURL"=>"https://yourdomain.com/callback.php", "AccountReference"=>"MediaForce", "TransactionDesc"=>"Website Payment" ]; if(isset($_POST['phone_number'])){ $phone=$_POST['phone_number']; $orderNo=$_POST['orderNo']; $amount=1; $phone=(substr($phone,0,1)=="0") ? preg_replace("/^0/","254",$phone) : $phone; $credentials= base64_encode( $config['key'].':'.$config['secret'] ); $url= "https://sandbox.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials"; $ch=curl_init($url); curl_setopt($ch,CURLOPT_HTTPHEADER,[ "Authorization: Basic ".$credentials ]); curl_setopt($ch,CURLOPT_RETURNTRANSFER,1); $response=curl_exec($ch); curl_close($ch); $result=json_decode($response); $token=$result->access_token; $timestamp=date("YmdHis"); $password=base64_encode( $config['BusinessShortCode']. $config['passkey']. $timestamp ); $request=[ "BusinessShortCode"=>$config['BusinessShortCode'], "Password"=>$password, "Timestamp"=>$timestamp, "TransactionType"=>$config['TransactionType'], "Amount"=>$amount, "PartyA"=>$phone, "PartyB"=>$config['BusinessShortCode'], "PhoneNumber"=>$phone, "CallBackURL"=>$config['CallBackURL'], "AccountReference"=>$config['AccountReference'], "TransactionDesc"=>$config['TransactionDesc'] ]; $endpoint= "https://sandbox.safaricom.co.ke/mpesa/stkpush/v1/processrequest"; $ch=curl_init($endpoint); curl_setopt($ch,CURLOPT_HTTPHEADER,[ "Authorization: Bearer ".$token, "Content-Type: application/json" ]); curl_setopt($ch,CURLOPT_POST,1); curl_setopt($ch,CURLOPT_POSTFIELDS,json_encode($request)); curl_setopt($ch,CURLOPT_RETURNTRANSFER,1); $response=curl_exec($ch); curl_close($ch); $result=json_decode($response,true); if($result['ResponseCode']=="0"){ $conn=new PDO( "mysql:host=localhost;dbname=mpesa", "root", "" ); $stmt=$conn->prepare( "INSERT INTO orders (OrderNo,Amount,Phone,CheckoutRequestID,MerchantRequestID) VALUES(?,?,?,?,?)" ); $stmt->execute([ $orderNo, $amount, $phone, $result['CheckoutRequestID'], $result['MerchantRequestID'] ]); $_SESSION['CheckoutRequestID'] = $result['CheckoutRequestID']; header("Location: confirm-payment.php"); exit(); }else{ $errmsg=$result['errorMessage']; } } ?>
When successful, Safaricom returns:
{
"ResponseCode":"0",
"MerchantRequestID":"...",
"CheckoutRequestID":"..."
}
A ResponseCode of 0 indicates success.
Store these identifiers in your database for future verification.
Step 6: Save Transaction Records in MySQL
Create a table for storing transaction details.
Recommended fields:
ID OrderNo Amount Phone MerchantRequestID CheckoutRequestID Status MpesaCode TransactionDate
Saving transaction records helps:
- Track payments
- Reconcile transactions
- Generate reports
- Handle disputes
Step 7: Handle MPESA Callback Responses
After the customer enters their PIN, Safaricom sends a response to your callback URL.
Your callback.php file should:
- Receive JSON data
- Decode the response
- Log transaction results
- Update your database
<?php
$data=file_get_contents('php://input');
$response=json_decode($data,true);
file_put_contents( 'transaction_log', date('Y-m-d H:i:s'). ' - '. $data. PHP_EOL, FILE_APPEND );
$conn=new PDO( "mysql:host=localhost;dbname=mpesa", "root", "" );
$callback= $response['Body']['stkCallback'];
$checkoutID= $callback['CheckoutRequestID'];
$status= ($callback['ResultCode']==0) ? 'SUCCESS' : 'FAILED';
$receipt=null;
if(isset($callback['CallbackMetadata'])){
foreach( $callback['CallbackMetadata']['Item'] as $item){
if( $item['Name'] == 'MpesaReceiptNumber' ){
$receipt= $item['Value']; }
}
}
$stmt=$conn->prepare( "UPDATE orders SET Status=?, MpesaCode=? WHERE CheckoutRequestID=?" );
$stmt->execute([ $status, $receipt, $checkoutID ]);
echo json_encode([ "ResultCode"=>0, "ResultDesc"=>"Accepted" ]);
Possible Result Codes
| Result Code | Meaning |
| 0 | Success |
| 1032 | User Cancelled |
| 1037 | Timeout |
| 1 | Insufficient Funds |
| Other | Error |
Example Workflow
Successful payment:
Customer Pays → Callback Received → Database Updated → Status = SUCCESS
Cancelled payment:
Customer Cancels → Callback Received → Status = CANCELLED
This ensures your system always reflects the correct payment status.
Step 8: Verify Transaction Status
Sometimes callback responses may be delayed.
To improve reliability, perform a Transaction Status Query. Create the page confirm-payment.php to enable the buyer verify their payment.
<?php session_start(); ?> <!DOCTYPE html> <html> <head> <title>Confirm Payment</title> </head> <body> <h2>Payment Processing</h2> <p> Check your phone and enter your MPESA PIN. </p> <p> Once completed click below. </p> <a href="status.php"> Check Payment Status </a> </body> </html>
Transaction verification confirms:
- Whether payment exists
- Amount paid
- Transaction code
- Payment status
Create status.php
<?php session_start(); $conn=new PDO( "mysql:host=localhost;dbname=mpesa", "root", "" ); $checkoutID= $_SESSION['CheckoutRequestID']; $stmt=$conn->prepare( "SELECT * FROM orders WHERE CheckoutRequestID=?" ); $stmt->execute([$checkoutID]); $order= $stmt->fetch(PDO::FETCH_ASSOC); ?> <!DOCTYPE html> <html> <head> <title>Status</title> </head> <body> <h2>Transaction Status</h2> <p> Status: <strong> <?php echo $order['Status']; ?> </strong> </p> <?php if($order['Status']=="SUCCESS"){ echo "<p>MPESA Code: ".$order['MpesaCode']."</p>"; } ?> </body> </html>
Verification should be done before:
- Activating accounts
- Granting access
- Processing orders
- Delivering products
Testing Your MPESA Integration
Before going live:
Test Phone Number
Use Safaricom Sandbox credentials.
Test Scenarios
- Successful payment
- User cancellation
- Incorrect PIN
- Insufficient funds
- Timeout
Ensure every scenario updates the database correctly.
Common MPESA STK Push Errors
Invalid Access Token
Cause:
- Wrong Consumer Key
- Wrong Consumer Secret
Solution:
Verify credentials in Daraja.
Invalid Callback URL
Cause:
- Localhost cannot receive callbacks.
Solution:
Use Ngrok during development.
Invalid Phone Number
Cause:
Incorrect format.
Solution:
Convert numbers to:
2547XXXXXXXX
STK Push Not Received
Cause:
- Network issues
- Customer unavailable
Solution:
Retry after a few minutes.
Security Best Practices
When implementing MPESA STK Push Integration in PHP:
- Never expose API credentials
- Store secrets in environment variables
- Use HTTPS
- Validate all inputs
- Log transaction activity
- Verify callback authenticity
- Sanitize database queries
Following these practices helps protect customer payments and sensitive business data.
Frequently Asked Questions
What is MPESA STK Push?
MPESA STK Push is a payment prompt sent directly to a customer’s phone allowing them to authorize payment using their MPESA PIN.
Can I integrate MPESA STK Push using PHP?
Yes. Safaricom Daraja API supports PHP through cURL requests and JSON responses.
Do I need a Daraja account?
Yes. A Daraja developer account is required to access MPESA APIs.
Is Sandbox free?
Yes. The Sandbox environment is completely free and intended for testing.
What is a Callback URL?
A Callback URL is an endpoint on your server that receives payment results from Safaricom after a transaction is completed.
Conclusion
Integrating MPESA STK Push in PHP is one of the most effective ways to accept online payments in Kenya. Through Safaricom’s Daraja API, businesses can initiate secure payment requests, automate transaction verification, and provide customers with a seamless checkout experience.
By following this tutorial, you have learned how to set up a complete MPESA STK Push Integration in PHP, including authentication, payment initiation, callback handling, transaction verification, and database storage.
Whether you’re building an e-commerce website, school management system, SACCO platform, booking portal, or SaaS application, MPESA integration can help streamline payments and improve customer satisfaction.
See also how to integrate it on Laravel: MPESA API Integration in Laravel