Singularity Payments LogoSingularity Payments
Integrations

Next.js

Singularity payments can be integrated into your Next.js application with ease.

Install the packages

npm install @singularity-payments/nextjs @singularity-payments/react
pnpm add @singularity-payments/nextjs @singularity-payments/react
yarn add @singularity-payments/nextjs @singularity-payments/react
bun add @singularity-payments/nextjs @singularity-payments/react

Install 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 ngrok
pnpm add -g ngrok
yarn add -g ngrok
bun add -g ngrok

Guide on Obtaining M-pesa Credentials

Learn how to obtain M-pesa credentials for your application.

Configure your environmental variables

.env.local
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/result

Understanding 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:

  1. Submit business registration documents
  2. Complete KYC verification
  3. Wait for approval (can take several days)
  4. 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 number

Configure the Mpesa SDK on the Server

lib/mpesa.ts
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.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
      },
    },
  },
);

Configure the Catch All Route

app/api/mpesa/[...mpesa]/route.ts
import { mpesa } from "@/lib/mpesa";

export const { POST } = mpesa.handlers.catchAll;

Configure the Mpesa SDK on the Client

lib/mpesa-client.ts
import { createMpesaClient } from "@singularity-payments/react";

export const mpesaClient = createMpesaClient({
  baseUrl: "", //If they are running on different ports you can include eg("http://localhost:3000").
});

Start Ngrok

The Daraja api requires a publically accessible callback url for callbacks

ngrok http 3000 # whatever port your server is running on

Copy paste the url from ngrok into the env variable MPESA_CALLBACK_URL.

.env.local
MPESA_CALLBACK_URL=https://your-ngrok-url.ngrok.free/api/mpesa/callback # make sure you include the /api/mpesa/callback

Use the Mpesa SDK on the Client

"use client";

import { mpesaClient } from "@/lib/mpesa-client";
import { useState } from "react";

export default function PaymentTest() {
  const [amount, setAmount] = useState(1);
  const [phoneNumber, setPhoneNumber] = useState("254712345678"); // This is the test phone number replace with your phone number
  async function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
    event.preventDefault();
    const { data, error } = await mpesaClient.stkPush({
      amount,
      phoneNumber,

      accountReference: "Singularity",
      transactionDesc: "Singularity Payment",
    });
    if (error) {
      console.error(error);
    } else {
      console.log(data);
    }
  }

  return (
    <form onSubmit={handleSubmit}>
      <label>
        Amount:
        <input
          type="number"
          value={amount}
          onChange={(e) => setAmount(Number(e.target.value))}
        />
      </label>
      <label>
        Phone Number:
        <input
          type="tel"
          value={phoneNumber}
          onChange={(e) => setPhoneNumber(e.target.value)}
        />
      </label>
      <button type="submit">Pay</button>
    </form>
  );
}

The core part of the SDK working here is

const { data, error } = await mpesaClient.stkPush({
  amount,
  phoneNumber,
  accountReference: "Singularity",
  transactionDesc: "Singularity Payment",
});
Edit on GitHub

On this page