Overview
Real-time event notifications for WAPI and Payments API.
WiPay delivers server-to-server event notifications to HTTPS endpoints you register for your merchant account. A single endpoint receives events for one API family; register a separate endpoint per family if you consume both.
| API family | Event prefix | Reference |
|---|---|---|
payments_api | payment.* | Payments API events |
wapi | withdrawal.* | WAPI events |
Delivery model
- Method:
POSTwithContent-Type: application/jsonandAccept: application/json. - Body: a JSON envelope (see below) sent as the raw request body.
- Acknowledge with any
2xxHTTP response as soon as you have durably accepted the event. - Any non-
2xx, connection error, or timeout is treated as a delivery failure and scheduled for retry. - The same delivery identifier is reused across retries; always treat the
idfield (and theX-WiPay-Webhook-Idheader) as an idempotency key.
Headers
Every request carries:
| Header | Purpose |
|---|---|
X-WiPay-Webhook-Event | Event type (e.g. payment.success). |
X-WiPay-Webhook-Id | Unique UUID for this event. Reused across retries. |
X-WiPay-Webhook-Signature | sha256=<hex> HMAC of the raw request body. |
X-WiPay-Webhook-Timestamp | Unix timestamp (seconds, UTC) when the request was signed. |
X-WiPay-Webhook-Version | Signature scheme version (currently v1). |
Envelope
{
"id": "3e1f9b2c-...-...-4a1e",
"api_family": "payments_api",
"event": "payment.success",
"occurred_at": "2026-04-17T15:04:03+00:00",
"data": { /* event-specific payload */ },
"meta": { /* event-specific context; may be empty */ }
}id is the same value as X-WiPay-Webhook-Id. data and meta keys vary by
event; consumers must be forward-compatible and ignore unknown keys.
Signature verification
- Read the raw request body exactly as received (do not reformat).
- Compute
HMAC-SHA256(body, endpointSecret)using the signing secret shown once when you created or rotated the endpoint. - Compare against the hex portion of
X-WiPay-Webhook-Signatureusing a constant-time comparison. - Reject requests with a missing, malformed, or mismatched signature.
Replay protection
X-WiPay-Webhook-Timestamp is the signing time. Reject requests older than
your tolerance window (five minutes is recommended) to mitigate replay.
Example (Node.js)
const crypto = require('crypto');
function verify(req, secret) {
const raw = req.rawBody; // Express: use express.raw({ type: 'application/json' })
const header = req.header('X-WiPay-Webhook-Signature') || '';
const [, received] = header.split('=');
if (!received || received.length !== 64) {
return false;
}
const expected = crypto
.createHmac('sha256', secret)
.update(raw)
.digest('hex');
return received && crypto.timingSafeEqual(
Buffer.from(expected, 'hex'),
Buffer.from(received, 'hex')
);
}Example (PHP)
$raw = file_get_contents('php://input');
$header = $_SERVER['HTTP_X_WIPAY_WEBHOOK_SIGNATURE'] ?? '';
[, $received] = array_pad(explode('=', $header, 2), 2, '');
$expected = hash_hmac('sha256', $raw, $endpointSecret);
if (!hash_equals($expected, $received)) {
http_response_code(400);
exit;
}Delivery retries
- WiPay retries failed deliveries automatically when your endpoint returns a
non-
2xx, times out, or cannot be reached. - Retries use backoff over a limited delivery window rather than a tight loop.
- Base retry cadence:
| Retry # | Nominal delay |
|---|---|
| 1 | 1 minute |
| 2 | 2 minutes |
| 3 | 5 minutes |
| 4 | 10 minutes |
| 5 | 15 minutes |
| 6 | 30 minutes |
| 7 | 1 hour |
| 8 | 2 hours |
| 9 | 4 hours |
| 10 | 8 hours |
| 11 and later | 8 hours |
- Actual retry timing can vary slightly, and retries stop once the delivery window closes.
- Repeated failures may cause WiPay to disable an endpoint until you review and re-enable it in the Developer page.
- A retry is a redelivery of the same logical event, so
X-WiPay-Webhook-Idremains stable across retry attempts.
Idempotency
X-WiPay-Webhook-Id(and the envelopeid) is stable across retries for a given logical event. Use it as the primary idempotency key in your handler.- Ignore events you have already processed. A delivered event may be observed more than once if your handler took longer than the timeout.
Managing endpoints
Register, rotate the secret, and inspect delivery history from the Developer page inside the WiPay dashboard. Endpoint URLs must use public HTTPS, must not contain embedded credentials, and must resolve to public internet-routable IP addresses. The signing secret is displayed only once at creation or rotation; store it in a secure secret manager.
If you send a test from the Developer page, WiPay delivers a webhook.test
event. Handle or ignore that event separately from your production workflows.