Webhooks
Get told when something happens. Event list, payload shape and how to verify the HMAC signature on every delivery.
https://api.paysasa.com/api/v1Authx-api-keyFormatJSONConfigure a webhook URL in your Business settings to receive payment event notifications. Paysasa sends a POST request to your endpoint for each event.
Event Types#
Customer completed payment
Session expired unpaid
Payment link payment received
Withdrawal sent to M-Pesa/PayPal
Withdrawal failed — funds returned
Wallet top-up confirmed
WaaS deposit credited to master wallet
WaaS withdrawal (M-Pesa or Paysasa wallet)
WaaS withdrawal failed — master wallet refunded
eTIMS invoice/receipt accepted by KRA
eTIMS document rejected by KRA
eFlow bill paid (confirm may still be pending)
eFlow adapter confirm succeeded
eFlow paid but adapter confirm failed — retryable
// Example: checkout.paid
{
"event": "checkout.paid",
"created_at": "2024-08-15T12:34:56.000Z",
"data": {
"session_id": "ses_01hx...",
"reference": "CHK-20240815-0042",
"amount": 15000,
"currency": "KES",
"payment_method": "m_pesa",
"metadata": { "orderId": "1042" }
}
}Verifying Webhooks#
Each webhook includes an x-paysasa-signature header — an HMAC-SHA256 of the raw request body signed with your webhook secret.
import { createHmac } from "crypto";
function verifyWebhook(rawBody: string, signature: string, secret: string): boolean {
const expected = createHmac("sha256", secret)
.update(rawBody)
.digest("hex");
// Use timingSafeEqual to prevent timing attacks
const a = Buffer.from(expected, "hex");
const b = Buffer.from(signature.replace("sha256=", ""), "hex");
if (a.length !== b.length) return false;
return require("crypto").timingSafeEqual(a, b);
}
// In your webhook handler (e.g. Next.js Route Handler):
export async function POST(req: Request) {
const rawBody = await req.text();
const sig = req.headers.get("x-paysasa-signature") ?? "";
if (!verifyWebhook(rawBody, sig, process.env.WEBHOOK_SECRET!)) {
return Response.json({ error: "Invalid signature" }, { status: 401 });
}
const event = JSON.parse(rawBody);
// Process event.event ...
return Response.json({ received: true });
}