📲 How to Integrate M-Pesa STK Push in Node.js (Step-by-Step Guide).

Table of contents
- What is M-Pesa STK Push?
- Prerequisites
- Project Setup
- Step 1: Initialize Your Project
- Step 2: Install Dependencies
- Step 3: Project Structure
- Step 4: Set Up Environment Variables
- Building the Application
- Step 1: Generate a Timestamp Utility
- Step 2: Generate an Access Token
- Step 4: Define Routes
- Step 5: Set Up the Server
- Step 1: Start the Server
- Step 2: Expose Your Local Server (Optional)
- Step 4: Check the Callback
- Best Practices
- Going Live
- Conclusion
Mobile money has revolutionized the way we handle transactions, especially in regions like East Africa, where M-Pesa, a mobile payment service by Safaricom, dominates the market. Launched in 2007, M-Pesa has become a cornerstone of financial inclusion in Kenya and beyond, enabling millions to send, receive, and pay for goods and services directly from their mobile phones. One of the most powerful features of M-Pesa for developers is the STK Push (Sim Toolkit Push), which allows businesses to initiate payment requests directly on a customer’s phone. In this blog, we’ll walk through how to integrate M-Pesa STK Push into a Node.js application, step by step, with detailed code examples and best practices.
What is M-Pesa STK Push?
M-Pesa STK Push is part of Safaricom’s Daraja API, a REST-based API that enables developers to integrate M-Pesa services into their applications. Specifically, STK Push (also known as M-Pesa Express) allows a business to prompt a customer to pay by sending a payment request directly to their phone. The customer then authorizes the transaction by entering their M-Pesa PIN on a pop-up menu, without needing to manually input the paybill number, account number, or amount. This seamless process is widely used in e-commerce platforms, bill payments, and other online services.
Here’s how it works at a high level:
Your application sends a payment request to Safaricom’s Daraja API.
Safaricom triggers an STK Push prompt on the customer’s phone, pre-filled with the payment details (amount, business name, etc.).
The customer enters their M-Pesa PIN to authorize the transaction.
Safaricom processes the payment, debits the customer’s M-Pesa account, and credits the business’s account.
Safaricom sends a callback to your application with the transaction status.
By the end of this guide, you’ll have a fully functional Node.js application that can initiate STK Push requests and handle transaction callbacks.
Prerequisites
Before we dive into the code, let’s ensure you have everything you need to get started:
Safaricom Developer Account: You’ll need to sign up on the Safaricom Developer Portal. Once registered, create an app to obtain your Consumer Key, Consumer Secret, and Passkey. For testing, you’ll use the sandbox environment, but for production, you’ll need to go live (more on that later).
Node.js and npm: Ensure you have Node.js (version 14 or higher) and npm installed on your machine. You can download them from nodejs.org.
A Safaricom Phone Number: For testing in the sandbox, you’ll need a Kenyan Safaricom phone number (starting with 2547…). You can use your own number or a test number provided by Safaricom.
Postman: We’ll use Postman to test our API endpoints. Download it from postman.com.
Ngrok (Optional for Testing): Since Safaricom requires a publicly accessible callback URL to send transaction results, you can use Ngrok to expose your local server to the internet during development. Alternatively, you can deploy your app to a cloud platform like Heroku, Netlify, or Vercel for testing.
Basic Knowledge of Node.js and Express: This guide assumes you’re familiar with JavaScript, Node.js, and the Express framework.
Project Setup
Let’s set up a Node.js project to integrate M-Pesa STK Push. Follow these steps to get your environment ready.
Step 1: Initialize Your Project
Create a new directory for your project and initialize a Node.js project:
bash
mkdir mpesa-stk-push
cd mpesa-stk-push
npm init -y
This creates a package.json file to manage your dependencies.
Step 2: Install Dependencies
We’ll need a few packages to build our application:
express: A lightweight web framework for Node.js to handle HTTP requests.
axios: For making HTTP requests to Safaricom’s Daraja API.
dotenv: To manage environment variables securely.
ngrok (optional): To expose your local server for testing callbacks.
Install them using npm:
bash
npm install express axios dotenv ngrok
Step 3: Project Structure
Organize your project with the following structure for clarity:
mpesa-stk-push/
├── controllers/
│ └── mpesaController.js
├── middlewares/
│ └── generateAccessToken.js
├── routes/
│ └── mpesaRoutes.js
├── utils/
│ └── timestamp.js
├── .env
├── server.js
└── package.json
controllers/mpesaController.js: Handles the business logic for STK Push and callback processing.
middlewares/generateAccessToken.js: Middleware to generate an access token for authenticating API requests.
routes/mpesaRoutes.js: Defines the API routes for STK Push and callbacks.
utils/timestamp.js: Utility to generate timestamps in the required format.
.env: Stores sensitive environment variables.
server.js: The main entry point for your application.
Step 4: Set Up Environment Variables
Create a .env file in the root directory to store your Safaricom credentials and other configurations. Replace the placeholders with your actual values from the Safaricom Developer Portal:
SAFARICOM_CONSUMER_KEY=your_consumer_key
SAFARICOM_CONSUMER_SECRET=your_consumer_secret
PASS_KEY=your_passkey
BUSINESS_SHORT_CODE=174379
PORT=3000
SAFARICOM_CONSUMER_KEY and SAFARICOM_CONSUMER_SECRET: Obtained from your Safaricom app.
PASS_KEY: Provided by Safaricom for generating the STK Push password.
BUSINESS_SHORT_CODE: The paybill or till number (use 174379 for sandbox testing).
PORT: The port your server will run on.
Note: Add .env to your .gitignore file to avoid exposing sensitive credentials when pushing your code to a repository like GitHub.
Building the Application
Now that our project is set up, let’s write the code to integrate M-Pesa STK Push. We’ll break this down into several parts: generating an access token, initiating the STK Push, and handling the callback.
Step 1: Generate a Timestamp Utility
Safaricom requires a timestamp in the format YYYYMMDDHHmmss (e.g., 20250327194000 for March 27, 2025, 19:40:00). Let’s create a utility to generate this.
In utils/timestamp.js:
javascript
const getTimestamp = () => {
const date = new Date();
const year = date.getFullYear();
const month = ("0" + (date.getMonth() + 1)).slice(-2);
const day = ("0" + date.getDate()).slice(-2);
const hours = ("0" + date.getHours()).slice(-2);
const minutes = ("0" + date.getMinutes()).slice(-2);
const seconds = ("0" + date.getSeconds()).slice(-2);
return `${year}${month}${day}${hours}${minutes}${seconds}`;
};
module.exports = { getTimestamp };
This function formats the current date and time into the required format.
Step 2: Generate an Access Token
Every request to the Daraja API requires an OAuth access token, which is valid for one hour. We’ll create a middleware to generate this token and attach it to the request object.
In middlewares/generateAccessToken.js:
javascript
const axios = require("axios");
const generateAccessToken = async (req, res, next) => {
try {
const consumerKey = process.env.SAFARICOM_CONSUMER_KEY;
const consumerSecret = process.env.SAFARICOM_CONSUMER_SECRET;
const auth = Buffer.from(`${consumerKey}:${consumerSecret}`).toString("base64");
const response = await axios.get(
"https://sandbox.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials",
{
headers: {
Authorization: `Basic ${auth}`,
},
}
);
req.safaricom_access_token = response.data.access_token;
next();
} catch (error) {
console.error("Error generating access token:", error.message);
res.status(500).json({
message: "Failed to generate access token",
error: error.message,
});
}
};
module.exports = { generateAccessToken };
This middleware:
Encodes the consumer key and secret in Base64 for Basic Authentication.
Makes a GET request to Safaricom’s OAuth endpoint to obtain an access token.
Attaches the token to the request object (req.safaricom_access_token) for use in subsequent steps.
Calls next() to pass control to the next middleware or route handler.
Step 3: Implement the STK Push Controller
Now, let’s write the logic to initiate the STK Push. This involves constructing a payload with the required parameters, generating a password, and sending the request to Safaricom.
In controllers/mpesaController.js:
javascript
const axios = require("axios");
const { getTimestamp } = require("../utils/timestamp");
const initiateSTKPush = async (req, res) => {
try {
const { amount, phone, orderId } = req.body;
// Validate input
if (!amount || !phone || !orderId) {
return res.status(400).json({
message: "Amount, phone, and orderId are required",
});
}
// Ensure phone number is in the correct format (2547...)
const formattedPhone = phone.startsWith("0")
? `254${phone.slice(1)}`
: phone;
const url = "https://sandbox.safaricom.co.ke/mpesa/stkpush/v1/processrequest";
const auth = `Bearer ${req.safaricom_access_token}`;
const timestamp = getTimestamp();
const password = Buffer.from(
`${process.env.BUSINESS_SHORT_CODE}${process.env.PASS_KEY}${timestamp}`
).toString("base64");
// Construct the payload
const payload = {
BusinessShortCode: process.env.BUSINESS_SHORT_CODE,
Password: password,
Timestamp: timestamp,
TransactionType: "CustomerPayBillOnline",
Amount: amount,
PartyA: formattedPhone,
PartyB: process.env.BUSINESS_SHORT_CODE,
PhoneNumber: formattedPhone,
CallBackURL: "https://your-callback-url/api/stkPushCallback",
AccountReference: "MyApp Payment",
TransactionDesc: "Payment for order",
};
const response = await axios.post(url, payload, {
headers: {
Authorization: auth,
},
});
res.status(200).json({
message: "STK Push initiated successfully",
data: response.data,
});
} catch (error) {
console.error("Error initiating STK Push:", error.message);
res.status(500).json({
message: "Failed to initiate STK Push",
error: error.message,
});
}
};
const stkPushCallback = async (req, res) => {
try {
const callbackData = req.body;
if (!callbackData.Body || !callbackData.Body.stkCallback) {
return res.status(400).json({
message: "Invalid callback data",
});
}
const {
MerchantRequestID,
CheckoutRequestID,
ResultCode,
ResultDesc,
CallbackMetadata,
} = callbackData.Body.stkCallback;
// Log the callback data
console.log("Callback received:", {
MerchantRequestID,
CheckoutRequestID,
ResultCode,
ResultDesc,
});
// Extract metadata if the transaction was successful (ResultCode 0)
if (ResultCode === 0 && CallbackMetadata) {
const meta = CallbackMetadata.Item.reduce((acc, item) => {
acc[item.Name] = item.Value;
return acc;
}, {});
const transactionDetails = {
phoneNumber: meta.PhoneNumber,
amount: meta.Amount,
mpesaReceiptNumber: meta.MpesaReceiptNumber,
transactionDate: meta.TransactionDate,
};
console.log("Transaction successful:", transactionDetails);
// TODO: Save transaction details to your database
} else {
console.log("Transaction failed:", ResultDesc);
}
res.status(200).json({
status: "success",
});
} catch (error) {
console.error("Error processing callback:", error.message);
res.status(500).json({
message: "Error processing callback",
error: error.message,
});
}
};
module.exports = { initiateSTKPush, stkPushCallback };
This controller has two main functions:
initiateSTKPush: Initiates the STK Push by sending a POST request to Safaricom’s STK Push endpoint. It constructs the payload with required fields like BusinessShortCode, Password (generated by encoding the shortcode, passkey, and timestamp in Base64), and CallBackURL.
stkPushCallback: Handles the callback from Safaricom, logging the transaction details and preparing them for storage in a database (you’ll need to implement the database logic based on your requirements).
Step 4: Define Routes
Let’s define the API routes to trigger the STK Push and receive callbacks.
In routes/mpesaRoutes.js:
javascript
const express = require("express");
const router = express.Router();
const { generateAccessToken } = require("../middlewares/generateAccessToken");
const { initiateSTKPush, stkPushCallback } = require("../controllers/mpesaController");
router.post("/stkPush", generateAccessToken, initiateSTKPush);
router.post("/stkPushCallback", stkPushCallback);
module.exports = router;
The /stkPush route uses the generateAccessToken middleware to authenticate the request before calling initiateSTKPush.
The /stkPushCallback route handles the callback from Safaricom.
Step 5: Set Up the Server
Finally, let’s create the main server file to tie everything together.
In server.js:
javascript
const express = require("express");
const dotenv = require("dotenv");
const mpesaRoutes = require("./routes/mpesaRoutes");
dotenv.config();
const app = express();
// Middleware to parse JSON and URL-encoded bodies
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// Routes
app.use("/api", mpesaRoutes);
// Error handling middleware
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({
message: "Something went wrong!",
error: err.message,
});
});
// Start the server
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
This sets up an Express server, loads environment variables, and mounts the M-Pesa routes under the /api prefix.
Testing the Application
Now that our application is ready, let’s test it.
Step 1: Start the Server
Run your server:
bash
node server.js
You should see the message: Server is running on port 3000.
Step 2: Expose Your Local Server (Optional)
Since Safaricom requires a publicly accessible callback URL, you can use Ngrok to expose your local server. Install Ngrok globally if you haven’t already:
bash
npm install -g ngrok
Then, start Ngrok to expose your server:
bash
ngrok http 3000
Ngrok will provide a public URL (e.g., https://1234-5678.ngrok.io). Update the CallBackURL in your initiateSTKPush function to use this URL (e.g., https://1234-5678.ngrok.io/api/stkPushCallback).
Note: Safaricom does not allow Ngrok URLs in production for security reasons. For production, deploy your app to a cloud platform and use a proper domain.
Step 3: Test the STK Push
Use Postman to send a POST request to http://localhost:3000/api/stkPush with the following JSON body:
json
{
"amount": "1",
"phone": "254712345678",
"orderId": "12345"
}
amount: The amount to charge (e.g., 1 KES for testing).
phone: The customer’s phone number in the format 2547….
orderId: A unique identifier for the transaction (you can use this to track the order in your database).
If successful, you’ll receive a response like this:
json
{
"message": "STK Push initiated successfully",
"data": {
"MerchantRequestID": "29115-34620561-1",
"CheckoutRequestID": "ws_CO_27032025194000987",
"ResponseCode": "0",
"ResponseDescription": "Success. Request accepted for processing",
"CustomerMessage": "Success. Request accepted for processing"
}
}
The phone number you provided will receive an STK Push prompt asking for the M-Pesa PIN. Enter the PIN (in the sandbox, use 123456 as the PIN for test numbers).
Step 4: Check the Callback
Once the transaction is processed, Safaricom will send a callback to your specified CallBackURL. If you’re using Ngrok, you’ll see the callback data logged in your terminal. A successful transaction callback might look like this:
json
{
"Body": {
"stkCallback": {
"MerchantRequestID": "29115-34620561-1",
"CheckoutRequestID": "ws_CO_27032025194000987",
"ResultCode": 0,
"ResultDesc": "The service request is processed successfully.",
"CallbackMetadata": {
"Item": [
{ "Name": "Amount", "Value": 1 },
{ "Name": "MpesaReceiptNumber", "Value": "PIR2G55OU2" },
{ "Name": "TransactionDate", "Value": 20250327194115 },
{ "Name": "PhoneNumber", "Value": 254712345678 }
]
}
}
}
}
If the transaction fails (e.g., the user cancels the request), the ResultCode will be non-zero, and ResultDesc will explain the failure (e.g., "Request canceled by user").
Best Practices
To ensure your M-Pesa STK Push integration is robust and secure, follow these best practices:
Validate Input: Always validate the amount, phone, and orderId to prevent invalid requests. Ensure the phone number is in the correct format (2547…).
Store Transaction Data: Save the CheckoutRequestID from the STK Push response and the transaction details from the callback in a database (e.g., MongoDB, PostgreSQL). This allows you to track the status of each transaction and handle disputes.
Handle Errors Gracefully: Implement proper error handling for network issues, invalid credentials, and failed transactions. Log errors for debugging but avoid exposing sensitive details to the client.
Secure Your Callback URL: In production, ensure your callback URL is secure (HTTPS) and protected against unauthorized access. Validate the callback data to ensure it’s coming from Safaricom.
Use a Queue for Callbacks: If your application handles a high volume of transactions, consider using a message queue (e.g., RabbitMQ, Redis) to process callbacks asynchronously.
Test Thoroughly in Sandbox: Before going live, test various scenarios in the sandbox, including successful payments, cancellations, and timeouts.
Go Live for Production: Once you’re ready to move to production, follow Safaricom’s Go Live guide to obtain production credentials. You’ll need to whitelist your server’s IP addresses and provide a production callback URL.
Going Live
To use M-Pesa STK Push in production:
Complete the Go Live process on the Safaricom Developer Portal.
Obtain your production credentials (Consumer Key, Consumer Secret, Passkey, and Business Short Code).
Update your .env file with the production credentials.
Change the API URLs from sandbox.safaricom.co.ke to api.safaricom.co.ke.
Deploy your application to a cloud platform and ensure your callback URL is publicly accessible and secure (HTTPS).
Conclusion
Integrating M-Pesa STK Push into your Node.js application opens up a world of possibilities for seamless mobile payments. In this guide, we’ve covered the entire process, from setting up your project to initiating STK Push requests and handling callbacks. By following best practices and thoroughly testing in the sandbox, you can build a robust payment system that enhances the user experience for your customers.
M-Pesa’s dominance in the mobile money space, especially in Kenya, makes it a critical tool for developers building applications for the East African market. Whether you’re running an e-commerce platform, a bill payment system, or a subscription service, STK Push can streamline your payment process and reduce friction for your users.
If you have any questions or run into issues, feel free to leave a comment below. Happy coding, and I hope this integration brings value to your application!
Subscribe to my newsletter
Read articles from Abdikhafar Issack directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
