Escrow Safe Trade is live: protect your next high-value deal Learn more
ProductsPayment links & checkoutM-Pesa, cards, invoices, split payoutsEscrow Safe TradeMilestone and release-based escrowWallets & P2PPersonal and business walletsChama, Lengo & DeniGroup savings, goals and IOUsBusiness toolkitAccounting, payroll, POS, ticketingBulk payments & payrollPay many people in one uploadSubscriptions & billingRecurring plans and renewalsRent, loans & donationsCollections and campaignsAll products
SolutionsMerchantsBuyersChama groupsBusiness solutionsIndustries
Pricing
DevelopersDeveloper overviewQuickstart, auth and sandboxREST APIsCheckout, invoices and requestsWebhooksSigned, real-time eventsWallet-as-a-ServiceVirtual sub-accountsDocumentationGuides and API reference
Security
CompanyAbout usCareersPartnersBlogHelp centreContact us
Log in Get started Talk to sales
Events & guides

Webhooks

Get told when something happens. Event list, payload shape and how to verify the HMAC signature on every delivery.

Base URLhttps://api.paysasa.com/api/v1Authx-api-keyFormatJSON

Configure 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#

checkout.paid

Customer completed payment

checkout.expired

Session expired unpaid

payment_link.paid

Payment link payment received

payout.completed

Withdrawal sent to M-Pesa/PayPal

payout.failed

Withdrawal failed — funds returned

topup.completed

Wallet top-up confirmed

waas.deposit.success

WaaS deposit credited to master wallet

waas.withdrawal.success

WaaS withdrawal (M-Pesa or Paysasa wallet)

waas.withdrawal.failed

WaaS withdrawal failed — master wallet refunded

etims.document.accepted

eTIMS invoice/receipt accepted by KRA

etims.document.rejected

eTIMS document rejected by KRA

eflow.payment.succeeded

eFlow bill paid (confirm may still be pending)

eflow.request.completed

eFlow adapter confirm succeeded

eflow.confirm.failed

eFlow paid but adapter confirm failed — retryable

JSON
// 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.

JavaScript
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 });
}