Kenya's home for African comic books — a full-stack Next.js demo that integrates K2 Connect (powered by Kopo Kopo) to process payments. It demonstrates STK push, payment links, send money, and real-time webhook handling.
The app is a fictional comic book store. The store pages are the canvas. The real point is the K2 Connect integration wired up in the background:
- STK Push — prompt a customer's phone to complete an M-Pesa payment instantly
- Payment Links — generate a shareable M-Pesa payment URL the customer pays at their convenience
- Send Money — disburse funds to a mobile wallet, till number, or paybill
- Webhooks — subscribe to K2 events and inspect received payloads in real time
Every API interaction calls the official k2-connect-node SDK. The frontend shows forms, results, and status checks for each flow.
- Next.js 15 (App Router, TypeScript)
- k2-connect-node 2.0.0 — KopoKopo's official Node.js SDK
- React 19, Tailwind CSS 4, shadcn/ui
- Sonner for toast notifications, next-themes for dark mode
- Node.js 18+ (or Bun)
- A KopoKopo sandbox account
- An ngrok (or similar) tunnel for local webhook development — K2 requires an HTTPS callback URL
1. Clone and install dependencies
git clone <repo-url>
cd k2-connect-react
npm install2. Create your environment file
cp .env.example .env.localThen fill in the values (see Environment Variables below).
3. Start the development server
npm run devOpen http://localhost:3000.
Create a .env.local file in the project root with the following:
K2_CLIENT_ID=
K2_CLIENT_SECRET=
K2_API_KEY=
K2_BASE_URL=https://sandbox.kopokopo.com
K2_TILL_NUMBER=
K2_CALLBACK_URL=
NEXT_PUBLIC_CALLBACK_URL=
| Variable | Where to get it |
|---|---|
K2_CLIENT_ID |
KopoKopo dashboard → App credentials |
K2_CLIENT_SECRET |
KopoKopo dashboard → App credentials |
K2_API_KEY |
KopoKopo dashboard → App credentials (used to verify webhook signatures) |
K2_BASE_URL |
Use https://sandbox.kopokopo.com for sandbox |
K2_TILL_NUMBER |
Your sandbox till number |
K2_CALLBACK_URL |
Your ngrok HTTPS URL + /api/webhooks/handler e.g. https://abc123.ngrok.io/api/webhooks/handler |
NEXT_PUBLIC_CALLBACK_URL |
Same ngrok URL (exposed to the browser for pre-filling webhook forms) |
app/
page.tsx # Comic store home
stk/page.tsx # STK Push demo
payment-links/page.tsx # Payment Links demo
send-money/page.tsx # Send Money demo
webhooks/page.tsx # Webhook subscription + live event viewer
api/
auth/token/ # GET — fetch K2 access token
stk/ # POST — initiate STK push
stk/status/ # GET — check STK push status
payment-links/ # POST — create payment link
payment-links/status/ # GET — check payment link status
send-money/ # POST — send money
send-money/status/ # GET — check send money status
webhooks/subscribe/ # POST — subscribe to a K2 event
webhooks/handler/ # POST — receives K2 webhook callbacks
webhooks/events/ # GET / DELETE — read or clear stored events
components/
store/ # Comic grid, cards, buy dialog
stk/ # STK push form and status display
payment-links/ # Payment link form and result
send-money/ # Send money form and status display
webhooks/ # Subscribe form, events table, status
layout/ # Header, theme provider
ui/ # shadcn/ui primitives
lib/
k2-client.ts # Initializes the K2 Connect SDK singleton
comics.ts # Hardcoded sample comic data
format-phone.ts # Normalizes Kenyan phone numbers to +254 format
webhook-store.ts # In-memory store for received webhook events
api-error.ts # Error serialization helpers
tools/
token-instance.ts # Caches access tokens and creates an axios instance
types/
k2-connect-node.d.ts # TypeScript declarations for the SDK
The user enters a phone number and amount. The app calls POST /api/stk, which calls StkService.initiateIncomingPayment() from the SDK. K2 sends an M-Pesa payment prompt to the customer's phone. The API returns a location URL you can poll with StkService.getStatus(location).
Phone numbers in 07xx or 01xx format are automatically normalized to the +254 international format before the request is sent.
The app calls POST /api/payment-links, which calls PaymentLinkService.createPaymentLink(). The API returns a URL the merchant can share with their customer. The customer opens the link and pays via M-Pesa at their own convenience.
The app calls POST /api/send-money, which calls SendMoneyService.sendMoney(). Three destination types are supported:
- Mobile Wallet — phone number + mobile network (Safaricom, Airtel, Telkom)
- Till Number — destination till account number
- Paybill — paybill number + account number
K2 pushes event notifications to the callback URL you register. The flow has two parts:
Subscribing — the app calls POST /api/webhooks/subscribe, which calls Webhooks.subscribe(). You choose an event type (e.g. buygoods_transaction_received), provide a callback URL, and a scope (till or company) with its reference.
Receiving — incoming POST requests arrive at /api/webhooks/handler. The handler verifies the K2 HMAC-SHA256 signature using K2_API_KEY before accepting the payload. Verified events are stored in memory (last 100 events). The Webhooks page polls GET /api/webhooks/events every 5 seconds and displays results in a live table.
Supported event types:
buygoods_transaction_receivedb2b_transaction_receivedcustomer_createdsettlement_transfer_completedm2m_transaction_completed
tools/token-instance.ts wraps the SDK's TokenService. It caches the access token in memory and refreshes it 60 seconds before expiry. An axios interceptor attaches the Authorization: Bearer <token> header to all outgoing requests automatically.
K2 requires an HTTPS URL for callbacks. During local development, use ngrok:
ngrok http 3000Copy the https:// forwarding URL and set it as K2_CALLBACK_URL and NEXT_PUBLIC_CALLBACK_URL in your .env.local. Restart the dev server after changing env variables.
This project is a reference integration of the k2-connect-node package. The SDK exposes the following services, all initialized from a single client instance in lib/k2-client.ts:
TokenService— OAuth2 access token managementStkService— incoming STK push paymentsPaymentLinkService— payment link creationSendMoneyService— outgoing disbursementsWebhooks— event subscriptions
See the K2 Connect developer docs for the full API reference.
npm run dev # development server on http://localhost:3000
npm run build # production build
npm start # run the production build
npm run lint # ESLint