Integrating M-Pesa Daraja 2.0 STK Push into a modern Next.js 15 App Router application often breaks in production due to three common mistakes: expired OAuth tokens, unverified callback payloads, and duplicate webhook processing.
Here is the clean, production-safe architecture pattern to handle Daraja 2.0 reliably in TypeScript.
1. Cache Your Daraja OAuth Access Token
Requesting a fresh OAuth token from Safaricom on every single STK Push adds latency and risks rate-limiting. Store the token in memory (or Redis) with a 55-minute TTL before its 3600-second expiry:
let cachedToken: { token: string; expiresAt: number } | null = null; export async function getDarajaAccessToken(): Promise<string> { if (cachedToken && Date.now() < cachedToken.expiresAt) { return cachedToken.token; } const credentials = Buffer.from( `${process.env.MPESA_CONSUMER_KEY}:${process.env.MPESA_CONSUMER_SECRET}` ).toString("base64"); const res = await fetch( "https://safaricom.co.ke", { headers: { Authorization: `Basic ${credentials}` }, cache: "no-store", } ); const data = await res.json(); cachedToken = { token: data.access_token, expiresAt: Date.now() + 55 * 60 * 1000, // 55 mins }; return cachedToken.token; } 2. Validate & Normalize Kenyan Phone Numbers (2547XXXXXXXX / 2541XXXXXXXX)
Always sanitize user input before generating the Base64 password (BusinessShortCode + Passkey + Timestamp):
export function formatKenyanPhone(phone: string): string { const cleaned = phone.replace(/\D/g, ""); if (cleaned.startsWith("0")) return `254${cleaned.slice(1)}`; if (cleaned.startsWith("254")) return cleaned; throw new Error("Invalid Kenyan phone number format"); } 3. Idempotent Callback Route Handler (app/api/mpesa/callback/route.ts)
Safaricom's servers will retry callbacks if your endpoint takes too long to respond. Always verify ResultCode === 0, extract MpesaReceiptNumber from CallbackMetadata.Item, check if the receipt already exists in your database, and immediately return { ResultCode: 0, ResultDesc: "Accepted" }.
⚡ Skip the Boilerplate Setup
If you want the complete, plug-and-play Next.js 15 M-Pesa Daraja 2.0 API Boilerplate & STK Push UI Component (including full TypeScript types, error states, and checkout UI), you can grab it here:
