Webhooks
Get notified in real time when payments, payouts and refunds change status.
Saukipay sends a POST request to your webhook URL whenever a transaction
changes status. Use webhooks to update orders and payouts without polling.
Set your webhook URL
Add your webhook URL on the Developer page of the
merchant dashboard, for example
https://api.yourdomain.com/webhook.
The URL must be publicly reachable. localhost URLs cannot receive events.
Validate the request
Every event carries an ApiKey header containing your secret key. Check it
before you process the event, and reject any request where it doesn't match.
import crypto from 'node:crypto';
import express from 'express';
const app = express();
app.use(express.json());
function isFromSaukipay(header: string | undefined) {
const received = Buffer.from((header ?? '').replace(/^ApiKey /, ''));
const expected = Buffer.from(process.env.SAUKIPAY_SECRET_KEY!);
return (
received.length === expected.length &&
crypto.timingSafeEqual(received, expected)
);
}
app.post('/webhook', (req, res) => {
if (!isFromSaukipay(req.header('ApiKey'))) {
return res.sendStatus(401);
}
// Acknowledge first, then do the work
res.sendStatus(200);
const event = req.body;
if (event.type === 'payment.success') {
// Verify the transaction, then fulfil the order
}
});Respond with 200 OK
- Return
200 OKas soon as you receive the event. If your handler does slow work, acknowledge first and process afterwards. - Any other response is recorded as a failed delivery.
- Missed events can be requested again through the API.
- Test your endpoint to confirm you receive the JSON body and return
200 OK.
Event types
| Event | When it's sent |
|---|---|
payment.success | A payment succeeded, on any channel |
payment.failed | A payment failed |
payment.pending | A payment is pending |
transfer.success | A payout was delivered |
transfer.failed | A payout failed |
refund.processed | A refund is being processed |
Example payload
{
"id": "67ec7641416a8374c865a167",
"type": "payment.success",
"created": "2025-09-03T10:40:18.742+00:00",
"data": {
"reference": "MFNRCBW0-SW-AFHJTTSA-M",
"status": "success",
"amount": 1025,
"settledAmount": 1000,
"chargedFee": 25,
"currency": "NGN",
"paymentChannel": "card",
"processorResponse": "Card 3DS verification successfully",
"ip_address": "203.0.113.24",
"paidAt": "2025-08-27 14:06:02",
"customer": {
"fullName": "John Doe",
"email": "john.doe@example.com",
"phoneNumber": "08012345678"
},
"paymentDetails": {
"method": "card",
"first6Digits": "123456",
"last4Digits": "1992",
"cardType": "master",
"expiry": "04/26"
},
"environment": "live",
"log": {
"errors": 0,
"success": true,
"channel": "card",
"history": [
{
"id": 8422,
"type": "TRANSACTION RECORDED",
"message": "success",
"reference": "METYZHJT-SW-LK12MUJCV7",
"channel": "S2S"
}
]
}
}
}The data object has the same shape as the
verify transaction
response.
Verify before you give value
Treat a webhook as a signal, not proof. Call
verify transaction
with the event's reference before you fulfil an order.