← Developers
Webhooks

Verify NganyaPay webhook events

NganyaPay webhooks allow SACCO systems, ERPs, finance platforms, and fleet tools to receive real-time transport-fintech events.

Webhook test endpoint: POST /api/v1/webhooks/test
Required scope: webhooks:test

Supported event types

payment.success
Passenger payment confirmed
payment.failed
Passenger payment failed
trip.started
Vehicle trip opened
trip.ended
Vehicle trip closed
vehicle.status.updated
Vehicle status changed
fuel.requested
Fuel request created
fuel.approved
Owner approved fuel request
fuel.rejected
Owner rejected fuel request
fuel.confirmed
Fuel payment confirmed
passenger.session.created
Passenger session initiated
passenger.session.paid
Passenger session paid

Webhook payload schema

{
  "id": "evt_123",
  "type": "payment.success",
  "environment": "test",
  "created_at": "2026-06-03T10:00:00Z",
  "data": {
    "object": "payment",
    "vehicle_id": 1,
    "trip_id": 16,
    "amount": "100.00",
    "currency": "KES"
  }
}

Test webhook payload

curl https://nganyapay.online/api/v1/webhooks/test \
  -X POST \
  -H "Authorization: Bearer ngp_sk_test_xxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "event_type": "payment.success",
    "data": {
      "vehicle_id": 1,
      "amount": "100.00",
      "currency": "KES"
    }
  }'
The public test endpoint validates the event format and returns a simulated event response.

Webhook headers

  • NganyaPay-Signature — HMAC SHA256 signature
  • NganyaPay-Timestamp — Unix timestamp used in signing
  • NganyaPay-Event — event type
  • NganyaPay-Request-Id — unique delivery request id
NganyaPay-Signature: v1=...
NganyaPay-Timestamp: 1779815029
NganyaPay-Event: payment.success
NganyaPay-Request-Id: whreq_xxxxxxxxx

Signature verification

The signed message is:

{timestamp}.{raw_request_body}

Compute HMAC SHA256 using your webhook signing secret and compare it to the NganyaPay-Signature header.

Always compare signatures using a constant-time comparison function.

Replay protection checklist

  • Read NganyaPay-Timestamp from the request headers.
  • Reject requests older than 300 seconds.
  • Verify NganyaPay-Signature before processing.
  • Store processed event.id values to prevent duplicate processing.
  • Return HTTP 2xx only after your system safely records the event.
if abs(current_unix_time - webhook_timestamp) > 300:
    reject_request("stale webhook timestamp")

if event_id_already_processed(event["id"]):
    return 200  # already handled safely

Node.js Express receiver

import express from "express";
import crypto from "crypto";

const app = express();

app.post(
  "/webhooks/nganyapay",
  express.raw({ type: "application/json" }),
  (req, res) => {
    const signingSecret = process.env.NGANYAPAY_WEBHOOK_SECRET;

    const signatureHeader = req.header("NganyaPay-Signature");
    const timestamp = req.header("NganyaPay-Timestamp");

    if (!signatureHeader || !timestamp) {
      return res.status(400).send("Missing signature headers");
    }

    const rawBody = req.body.toString("utf8");
    const digest = crypto
      .createHmac("sha256", signingSecret)
      .update(`${timestamp}.${rawBody}`)
      .digest("hex");

    const expected = `v1=${digest}`;

    if (
      !crypto.timingSafeEqual(
        Buffer.from(signatureHeader),
        Buffer.from(expected)
      )
    ) {
      return res.status(400).send("Invalid signature");
    }

    const event = JSON.parse(rawBody);
    console.log("Verified NganyaPay event:", event.type, event.id);

    return res.status(200).json({ received: true });
  }
);

app.listen(3000);