Callbacks (Webhooks)
After a payment is processed on a virtual account, KongaPay sends an automatic POST notification to your callback URL.
This is the primary and recommended mechanism for confirming payments. Build your integration around callbacks and use status polling only as a fallback.
Callback URL Priority
KongaPay resolves the callback URL in the following order:
| Priority | Source | When Used |
|---|---|---|
| 1 | Integration config URL | For static and static-flex accounts โ uses the URL set in your integration settings. |
| 2 | Default integration URL | Fallback if no specific config URL is set. |
| 3 | Per-VA callback_url | The URL passed in the request body when creating a nonstatic virtual account. |
Callback Payload
{
"status": "success",
"session_id": "090405261234567890",
"reference": "INV-2026-0001",
"amount": 5000,
"date": "2026-03-06 14:23:11"
}Payload Fields
| Field | Description |
|---|---|
| status | Payment outcome. Only process if "success". |
| session_id | NIP session ID. Use as a deduplication key โ reject duplicate callbacks for the same session. |
| reference | Your external_reference โ link this to your internal order or record. |
| amount | Confirmed transaction amount in Naira. |
| date | Timestamp of the payment in YYYY-MM-DD HH:MM:SS format. |
Handling Callbacks
Validate the incoming payload
Verify the signature header to confirm the request originated from KongaPay.
Check the status field
Only process a payment if status is "success".
Match on external_reference
Use this to link the payment to your internal order or record.
Use session_id as a deduplication key
Reject duplicate callbacks for the same session.
Respond with HTTP 200
Return a 200 status code promptly. KongaPay will retry failed callbacks.
Callback Handler Example
app.post('/webhook', express.json(), (req, res) => {
// 1. Validate signature
const signature = req.headers['x-kongapay-signature'];
if (!isValidSignature(signature, req.body)) {
return res.status(401).send('Invalid signature');
}
const { status, session_id, reference, amount } = req.body;
// 2. Only process successes
if (status !== 'success') return res.sendStatus(200);
// 3. Deduplicate
if (await db.sessionExists(session_id)) return res.sendStatus(200);
// 4. Reconcile
await db.markOrderPaid(reference, { amount, session_id });
// 5. Respond 200 promptly
res.sendStatus(200);
});