Events
Webhooks
Receive real-time notifications when a transaction status changes instead of polling the status API. Every delivery is signed so you can verify it came from Paylor.
Merchant webhooks
Paylor posts outbound events to the URLs registered at /api/v1/global/webhooks, or to the callbackUrl you send with a payment request.
1. EndpointCreate an HTTPS POST endpoint on your server (e.g. /api/paylor-callback).
2. SecretCopy the “Webhook ID (Secret)” attached to your API key in Dashboard > Developers > API Keys.
3. RegisterProvide a callbackUrl in the payment request body, or register the endpoint in the Global dashboard.
4. VerifyRecompute the HMAC SHA-256 signature on every delivery before trusting the payload.
1{2 "event": "global.collection.confirmed",3 "reference": "GC-1780000000000-A9X2K",4 "merchantReference": "INV-1042",5 "amount": 1500,6 "currency": "KES",7 "status": "CONFIRMED",8 "provider": "SASAPAY_KE",9 "createdAt": "2026-06-29T18:20:00.000Z"10}Payment events
Local payment and crypto callbacks deliver payment.success or payment.failed with the full transaction record. Registered webhook endpoints also receive transaction.updated.
1{2 "event": "payment.success",3 "transaction": {4 "id": "665f4c1b9a8d2e001f2a1234",5 "reference": "ORDER-12345",6 "internalReference": "PAYL-ROUTE-REF",7 "amount": 1000,8 "status": "COMPLETED",9 "providerRef": "ws_CO_...",10 "metadata": {11 "mpesaReceipt": "RFL8S2K9P0"12 }13 }14}reference is always your original merchant reference. internalReferenceis Paylor's channel/routing reference — reconcile on reference.1import express from 'express';2import crypto from 'crypto';3 4const app = express();5// Keep the raw body so the signature is verified over the exact bytes sent6app.use(express.json({7 verify: (req, _res, buf) => { req.rawBody = buf; }8}));9 10app.post('/paylor-callback', (req, res) => {11 const signature = req.headers['x-webhook-signature'];12 const secret = process.env.PAYLOR_WEBHOOK_SECRET;13 const expected = crypto14 .createHmac('sha256', secret)15 .update(req.rawBody)16 .digest('hex');17 if (signature !== expected) {18 return res.status(401).json({ error: 'Invalid signature' });19 }20 const { event, transaction } = req.body;21 if (event === 'payment.success') {22 console.log('Payment received:', transaction.reference);23 }24 res.json({ received: true });25});Signature verification
Verify every delivery before trusting it. Local payment callbacks and Global webhooks use different header names but the same HMAC SHA-256 scheme.
1X-Webhook-Signature: hex_hmac_sha256_of_raw_body2X-Webhook-Event: payment.success1X-Paylor-Signature: hex_hmac_sha256_of_raw_body2X-Paylor-Event: global.collection.confirmedThe signature is computed over the exact JSON bytes Paylor sends. Always verify against the raw request body (before any JSON parsing/re-serialization) using your Webhook Secret. Respond with HTTP 200 quickly; do slow processing asynchronously. Provider inbound callbacks are separate and use
/api/v1/global/callbacks/:rail.Status models
Use status transitions for reconciliation and user-facing order state.
| Status | Meaning |
|---|---|
PENDING | Provider request has been created and is waiting for callback confirmation. |
CONFIRMED | Collection succeeded. On instant rails (mobile money) funds credit the Global Wallet immediately; on delayed rails (e.g. Paystack cards, T+1/T+2) funds stay pending until the provider settles — the webhook includes settlesAt. |
SETTLED | A delayed-settlement collection's window elapsed and funds moved to the available balance (collection.settled webhook). |
REQUESTED | Withdrawal request has been created and funds may be held. |
PROCESSING | Provider accepted the withdrawal request and is processing payout. |
COMPLETED | Provider confirmed payout success. |
FAILED | Provider rejected the request or callback reported failure. |