Quick Start
Get started with M-Pesa payments in your app in minutes.
Installation
Install both the Client and Server package client package
Server-Side
npm install @singularity-payments/nextjspnpm add @singularity-payments/nextjsyarn add @singularity-payments/nextjsbun add @singularity-payments/nextjsnpm install @singularity-payments/sveltekitpnpm add @singularity-payments/sveltekityarn add @singularity-payments/sveltekitbun add @singularity-payments/sveltekitnpm install @singularity-payments/nuxtpnpm add @singularity-payments/nuxtyarn add @singularity-payments/nuxtbun add @singularity-payments/nuxtnpm install @singularity-payments/elysiapnpm add @singularity-payments/elysiayarn add @singularity-payments/elysiabun add @singularity-payments/elysianpm install @singularity-payments/fastifypnpm add @singularity-payments/fastifyyarn add @singularity-payments/fastifybun add @singularity-payments/fastifybash npm install @singularity-payments/fastify bash pnpm add @singularity-payments/fastify bash yarn add @singularity-payments/fastify bash bun add @singularity-payments/fastify npm install @singularity-payments/honopnpm add @singularity-payments/honoyarn add @singularity-payments/honobun add @singularity-payments/honoClient-Side
Security Warning
Client-side packages require a backend server. Never expose M-Pesa credentials in client-side code, these packages are designed to simplify integration with the backend
npm install @singularity-payments/reactpnpm add @singularity-payments/reactyarn add @singularity-payments/reactbun add @singularity-payments/reactnpm install @singularity-payments/vuepnpm add @singularity-payments/vueyarn add @singularity-payments/vuebun add @singularity-payments/vuenpm install @singularity-payments/sveltepnpm add @singularity-payments/svelteyarn add @singularity-payments/sveltebun add @singularity-payments/svelteSetup Ngrok for Testing
M-Pesa requires a public callback URL to send payment notifications. For local development, use ngrok:
Install ngrok from ngrok.com or using a node package manager:
npm install -g ngrokpnpm add -g ngrokyarn global add ngrokbun add -g ngrokStart ngrok on your dev port:
ngrok http 3000Copy the forwarding URL (e.g., https://abc123.ngrok-free.app)
Keep ngrok running while testing. M-Pesa callbacks will fail without it.
Environment Setup
Create a .env.local file in your project root:
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'
APP_URL=https://your-app-url.comNever commit your .env file to version control. Add it to .gitignore.
Configure M-Pesa Client
Create a server configuration file:
import { createMpesa } from "@singularity-payments/nextjs";
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.NEXT_PUBLIC_APP_URL}/api/mpesa/callback`,
},
{
callbackOptions: {
onSuccess: async (data) => {
console.log("Payment successful:", data);
},
onFailure: async (data) => {
console.log("Payment failed:", data);
},
},
},
);import { createMpesa } from '@singularity-payments/sveltekit';
import {
MPESA_CONSUMER_KEY,
MPESA_CONSUMER_SECRET,
MPESA_PASSKEY,
MPESA_SHORTCODE
} from '$env/static/private';
export const mpesa = createMpesa(
{
consumerKey: MPESA_CONSUMER_KEY,
consumerSecret: MPESA_CONSUMER_SECRET,
passkey: MPESA_PASSKEY,
shortcode: MPESA_SHORTCODE,
environment: 'sandbox',
callbackUrl: `https://your-domain.ngrok-free.dev/api/mpesa/callback`
},
{
callbackOptions: {
onSuccess: async (data) => {
console.log('Payment successful:', data);
},
onFailure: async (data) => {
console.log('Payment failed:', data);
}
}
}
);import { createMpesa } from "@singularity-payments/nuxt";
const config = useRuntimeConfig();
export const mpesa = createMpesa(
{
consumerKey: config.mpesaConsumerKey,
consumerSecret: config.mpesaConsumerSecret,
passkey: config.mpesaPasskey,
shortcode: config.mpesaShortcode,
environment: "sandbox",
callbackUrl: `https://your-domain.ngrok-free.dev/api/mpesa/callback`,
},
{
callbackOptions: {
onSuccess: async (data) => {
console.log("Payment successful:", data);
},
onFailure: async (data) => {
console.log("Payment failed:", data);
},
},
},
);import { Elysia } from "elysia";
import { cors } from "@elysiajs/cors";
import { createMpesa } from "@singularity-payments/elysia";
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: "sandbox",
callbackUrl: `https://your-domain.ngrok-free.dev/api/mpesa/callback`,
});
const app = new Elysia()
.use(
cors({
origin: "http://localhost:3000",
credentials: true,
allowedHeaders: ["Content-Type", "Authorization"],
methods: ["GET", "POST", "OPTIONS"],
}),
)
.get("/", () => "M-Pesa Payment API")
.get("/health", () => ({ status: "ok" }))
.group("/api/mpesa", (app) => app.use(mpesa.app))
.listen(3004);
console.log("Registered routes:");
app.routes.forEach((route) => {
console.log(`${route.method} ${route.path}`);
});
console.log(
`Elysia is running at ${app.server?.hostname}:${app.server?.port}`,
);import "dotenv/config";
import express from "express";
import cors from "cors";
import { createMpesa } from "@singularity-payments/express";
const app = express();
app.use(
cors({
origin: ["http://localhost:3000", "http://localhost:3001"],
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 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: "sandbox",
callbackUrl: `https://your-domain.ngrok-free.dev/api/mpesa/callback`,
});
const mpesaRouter = express.Router();
mpesa.router(mpesaRouter);
app.use("/api/mpesa", mpesaRouter);
app.use((err, req, res, next) => {
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");
console.log("Environment variables loaded:", {
hasConsumerKey: !!process.env.MPESA_CONSUMER_KEY,
hasConsumerSecret: !!process.env.MPESA_CONSUMER_SECRET,
hasPasskey: !!process.env.MPESA_PASSKEY,
hasShortcode: !!process.env.MPESA_SHORTCODE,
});
});import Fastify from "fastify";
import cors from "@fastify/cors";
import { config } from "dotenv";
import { createMpesa } from "@singularity-payments/fastify";
config();
const fastify = Fastify({
logger: true,
});
fastify.register(cors, {
origin: "http://localhost:3000",
credentials: true,
});
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: "sandbox",
callbackUrl: `https://your-domain.ngrok-free.dev/api/mpesa/callback`,
});
fastify.register(
async (instance) => {
mpesa.router(instance);
},
{ prefix: "/api/mpesa" },
);
const start = async () => {
try {
await fastify.listen({ port: 3003, host: "0.0.0.0" });
console.log("Server running on port 3003");
} catch (err) {
fastify.log.error(err);
process.exit(1);
}
};
start();import { Hono } from "hono";
import { serve } from "@hono/node-server";
import { config } from "dotenv";
import { createMpesa } from "@singularity-payments/hono";
import { cors } from "hono/cors";
config();
const app = new Hono();
app.use("/api/*", cors());
app.use(
"/api/*",
cors({
origin: "http://localhost:3000",
allowHeaders: ["X-Custom-Header", "Upgrade-Insecure-Requests"],
allowMethods: ["POST", "GET", "OPTIONS"],
exposeHeaders: ["Content-Length", "X-Kuma-Revision"],
maxAge: 600,
credentials: true,
}),
);
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: "sandbox",
callbackUrl: `https://your-domain.ngrok-free.dev/api/mpesa/callback`,
});
app.route("/api/mpesa", mpesa.app);
serve({
fetch: app.fetch,
port: 3002,
});
console.log("Server running on port 3002");Create Catch All Route Route ( Only for Next.js SvelteKit, Nuxt.js)
Create a catch-all API route to handle all M-Pesa operations. This only applies to:
- SvelteKit
- Next.js
- Nuxt.js
import { mpesa } from "~/lib/mpesa";
export const { POST } = mpesa.handlers.catchAll;import { mpesa } from '$lib/mpesa';
export const POST = mpesa.handlers.catchAll.POST;import { mpesa } from "../../utils/mpesa";
export default mpesa.handlers.catchAll;This single route handles all M-Pesa operations including callbacks, payment initiation, and status queries.
Make the client hander
import { createMpesaClient } from "@singularity-payments/react";
export const mpesaClient = createMpesaClient({
baseUrl: "http://localhost:3000", // your backend url(Use "" if the backend and frontend are on the same port)
});import { createMpesaClient } from "@singularity-payments/svelte";
export const mpesaClient = createMpesaClient({
baseUrl: "http://localhost:3000", // your backend url(Use "" if the backend and frontend are on the same port)
});import { createMpesaClient } from "@singularity-payments/vue";
export const mpesaClient = createMpesaClient({
baseUrl: "http://localhost:3000", // your backend url(Use "" if the backend and frontend are on the same port)
});Initiate an STK Push
Create a client component for handling payments:
// make sure to import mpesaClient
const { data, error } = await mpesaClient.stkPush({
amount: 1,
phoneNumber: phone,
accountReference: "singularity",
transactionDesc: "Quick Start",
});Example
"use client";
import { mpesaClient } from "~/lib/mpesa-client";
export default function PaymentForm() {
const handlePay = async () => {
const { data, error } = await mpesaClient.stkPush({
amount: 1,
phoneNumber: "+254740185793",
accountReference: "singularity",
transactionDesc: "Quick Start",
});
};
return (
<div>
<button onClick={handlePay}>Pay</button>
</div>
);
}Test Your Integration
Start your development server
npm run devStart ngrok in another terminal
ngrok http 3000Update your .env.local with the ngrok URL and restart your dev server
Navigate to your page with payment integrated
Initiate the transaction and check your phone for the STK Push prompt
Enter your M-Pesa PIN to complete the payment
In sandbox mode, use the test credentials provided by Safaricom Daraja.
What's Next
- Explore API Reference