Quickstart
Run your first TeronaPay payment in about five minutes. By the end of this page you will:
- Have a TeronaPay merchant and a sandbox API key.
- Send a real M-Pesa STK Push to a sandbox phone.
- Receive a signed webhook confirming the payment succeeded.
1. Create a merchant
Sign up at /signup. The signup flow:
- Creates a
merchantrecord. - Creates a
userrecord (your email + bcrypt-hashed password). - Issues your first API key (label
dashboard).
The plaintext secret is shown once at signup and on each login. Copy it to your environment now:
export NOWPESA_KEY_ID=np_xxxxxxxxxxxxxxxxxxxxxxxx
export NOWPESA_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx2. Take a payment
Send an M-Pesa STK Push to a sandbox-registered phone number (+254708374149 works in Daraja sandbox):
curl https://api.sandbox.teronapay.com/v1/payments \
-u "$NOWPESA_KEY_ID:$NOWPESA_SECRET" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-0001" \
-d '{
"reference": "order-0001",
"amount": 100.00,
"currency": "KES",
"channel": "mpesa_stk_push",
"payer_phone": "+254708374149"
}'The response comes back immediately:
{
"id": "8d6ff4fa-da3d-4006-8997-d2780d0e22fe",
"reference": "order-0001",
"amount": 100.00,
"currency": "KES",
"channel": "mpesa_stk_push",
"status": "processing",
"created_at": "2026-05-19T10:23:11.512Z"
}processing means we've initiated the STK Push and we're waiting on the customer to enter their PIN. The final state (succeeded or failed) arrives via webhook a few seconds later — or up to 60 seconds if the customer dismisses the prompt.
3. Receive the webhook
Register an endpoint that TeronaPay will POST signed events to:
curl https://api.sandbox.teronapay.com/v1/webhooks \
-u "$NOWPESA_KEY_ID:$NOWPESA_SECRET" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-domain.com/webhooks/nowpesa",
"event_types": ["*"]
}'The response includes the signing secret — store it. You'll need it to verify incoming requests:
{
"id": "66d5050b-1cd5-4953-b93b-f4ae9fee3fd9",
"url": "https://your-domain.com/webhooks/nowpesa",
"event_types": ["*"],
"signing_secret": "whsec_dff0fa03f70e1e7746a32f791a31057103e71181239c74777e78095df92f7f5a"
}We do a one-time verification POST to your URL before activating delivery — your endpoint must respond 2xx to that probe.
4. Verify the signature
Every delivery includes the X-Nowpesa-Signature header with shape t=<unix>,v1=<hex_hmac>. Verify like this (Node.js):
import { createHmac, timingSafeEqual } from "node:crypto";
function verify(rawBody: string, header: string, secret: string): boolean {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const t = parts.t;
const sig = parts.v1;
if (!t || !sig) return false;
const expected = createHmac("sha256", secret)
.update(`${t}.${rawBody}`)
.digest("hex");
return timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
}If verify() returns false, reject the request with 401. Otherwise process the event and respond 2xx.
5. Watch it work
In your dashboard:
/paymentsshows the order with status flipping fromprocessing→succeeded./webhooksshows the delivery log + signed request body./statementshows your running balance (now +KES 1.00).
That's the whole loop. From here:
- Read Concepts → Payments for the full payment lifecycle.
- Read Concepts → Webhooks for retry semantics, secret rotation, and per-payment callbacks.
- Browse the REST API reference for every endpoint.
Alternative to endpoint registration: if you don't want a persistent endpoint, pass
callback_urldirectly onPOST /v1/payments. Events for that payment go only to the specified URL, signed with your callback signing secret.
