← 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:
Required scope:
POST /api/v1/webhooks/test
Required scope:
webhooks:test
Supported event types
payment.successPassenger payment confirmed
payment.failedPassenger payment failed
trip.startedVehicle trip opened
trip.endedVehicle trip closed
vehicle.status.updatedVehicle status changed
fuel.requestedFuel request created
fuel.approvedOwner approved fuel request
fuel.rejectedOwner rejected fuel request
fuel.confirmedFuel payment confirmed
passenger.session.createdPassenger session initiated
passenger.session.paidPassenger 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 signatureNganyaPay-Timestamp— Unix timestamp used in signingNganyaPay-Event— event typeNganyaPay-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-Timestampfrom the request headers. - Reject requests older than 300 seconds.
- Verify
NganyaPay-Signaturebefore processing. - Store processed
event.idvalues 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);