Skip to main content

Webhooks

Purchases that settle asynchronously (status Processing) resolve in the background. Rather than poll, point us at a URL and we'll POST you the terminal result.

Set your webhook URL (and you'll be given a signing secret) from your dashboard.

Events

EventWhen
transaction.successA purchase was delivered
transaction.failedA purchase failed and was refunded to your wallet

Payload

We POST JSON like:

{
"event": "transaction.success",
"reference": "PLS-9KQ2M7P…",
"clientReference": "order-1001",
"status": "Success",
"service": "Data",
"amount": 27000,
"mobileNumber": "08030000000",
"providerReference": "118682181",
"completedAt": "2026-06-12T10:14:05Z"
}

amount is in kobo. Match reference — or your own clientReference — to the original purchase. clientReference is the idempotency key you sent, so you can correlate the event to your order even if the original purchase response never reached you.

Verifying the signature

Every delivery carries an HMAC signature so you can be sure it came from us:

X-Plustive-Signature: sha256=<hex>

It's HMAC-SHA256 of the raw request body, keyed by your webhook secret. Recompute it and compare in constant time:

import crypto from 'node:crypto';

function verify(rawBody, header, secret) {
const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
const a = Buffer.from(expected);
const b = Buffer.from(header ?? '');
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
tip

Verify against the raw body bytes, before any JSON parsing/re-serialization — reformatting changes the signature.

Responding

Return any 2xx to acknowledge. Non-2xx (or a timeout) is retried with backoff, so make your handler idempotent — key on reference.