Express
Singularity payments can be integrated into your Express application with ease.
Install the packages
npm install @singularity-payments/express dotenvpnpm add @singularity-payments/express dotenvyarn add @singularity-payments/express dotenvbun add @singularity-payments/express dotenvInstall Ngrok
The API requires a publically accessible url for callbacks. You can use ngrok to expose your local development server to the internet.
npm install -g ngrokpnpm add -g ngrokyarn add -g ngrokbun add -g ngrokGuide on Obtaining M-pesa Credentials
Learn how to obtain M-pesa credentials for your application.
Configure your environmental variables
MPESA_CONSUMER_KEY=your_consumer_key
MPESA_CONSUMER_SECRET=your_consumer_secret
MPESA_SHORTCODE=174379 #For STK Push Testing in Sandbox(Replace with your shortcode in production)
MPESA_PASSKEY=your_passkey
MPESA_ENVIRONMENT=sandbox # or 'production'
MPESA_CALLBACK_URL=https://your-app-url.com/api/mpesa/callback
# For STK Push you can just include the 6 above
# Required for B2C, B2B, Reversal, Transaction Status, and Account Balance
MPESA_INITIATOR_NAME=testapi #Initiator Name for Sandbox
MPESA_SECURITY_CREDENTIAL=your_security_credential
MPESA_RESULT_URL=https://your-app-url.com/api/mpesa/resultUnderstanding Shortcodes
Your shortcode is your business number in the M-Pesa system, it's what customers see when making payments.
Sandbox (Testing)
Use M-Pesa's provided test shortcodes:
174379- For STK Push testing
These are pre-configured and ready to use immediately.
Production (Live)
You'll need to apply for your own shortcode through Safaricom's M-Pesa portal:
- Submit business registration documents
- Complete KYC verification
- Wait for approval (can take several days)
- Receive your unique 5-7 digit shortcode
Important: Never mix sandbox and production credentials. Always use the matching shortcode for your environment.
# Sandbox
MPESA_SHORTCODE=174379
# Production
MPESA_SHORTCODE=123456 # Your approved business numberConfigue the SDK
import { createMpesa } from "@singularity-payments/express";
import { config } from "dotenv";
config();
export const mpesa = createMpesa(
{
consumerKey: process.env.MPESA_CONSUMER_KEY!,
consumerSecret: process.env.MPESA_CONSUMER_SECRET!,
passkey: process.env.MPESA_PASSKEY!,
shortcode: process.env.MPESA_SHORTCODE!,
environment:
(process.env.MPESA_ENVIRONMENT as "sandbox" | "production") || "sandbox",
callbackUrl: process.env.MPESA_CALLBACK_URL!,
// Required for B2C, B2B, Reversal, Transaction Status, and Account Balance
initiatorName: "testapi",
securityCredential: process.env.MPESA_SECURITY_CREDENTIAL!,
resultUrl: process.env.MPESA_RESULT_URL!,
timeoutUrl: process.env.MPESA_TIMEOUT_URL!,
},
{
callbackOptions: {
onSuccess: async (data) => {
console.log("Payment successful:", {
amount: data.amount,
phone: data.phoneNumber,
receipt: data.mpesaReceiptNumber,
transactionDate: data.transactionDate,
});
// TODO: Save to database
// await db.transaction.update({
// where: { CheckoutRequestID: data.CheckoutRequestID },
// data: { status: 'completed', mpesaReceiptNumber: data.mpesaReceiptNumber }
// });
},
onFailure: async (data) => {
console.log(" Payment failed:", {
resultCode: data.resultCode,
resultDesc: data.resultDescription,
});
// TODO: Update database
},
},
},
);Mount it to the route
const mpesaRouter = express.Router();
mpesa.router(mpesaRouter);
app.use("/api/mpesa", mpesaRouter);You can see that in this example
import express from "express";
import cors from "cors";
import { mpesa } from "./utils/mpesa.js";
const app = express();
app.use(
cors({
origin: ["http://localhost:5173", "http://localhost:3000"],
credentials: true,
methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"],
allowedHeaders: ["Content-Type", "Authorization", "X-Requested-With"],
preflightContinue: false,
optionsSuccessStatus: 204,
}),
);
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use((req, res, next) => {
console.log(`${req.method} ${req.path}`, req.body);
next();
});
const mpesaRouter = express.Router();
mpesa.router(mpesaRouter);
app.use("/api/mpesa", mpesaRouter);
app.use((err, res) => {
console.error("Error:", err);
res.status(500).json({
success: false,
error: err.message || "Internal server error",
});
});
app.listen(3001, () => {
console.log("Server running on port 3001");
});Start Ngrok
The Daraja api requires a publically accessible callback url for callbacks
ngrok http 3000 # whatever port your server is running onCopy paste the url from ngrok into the env variable MPESA_CALLBACK_URL.
MPESA_CALLBACK_URL=https://your-ngrok-url.ngrok.free/api/mpesa/callback # make sure you include the /api/mpesa/callback