Docs

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.
Collection confirmedjson
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.

payment.success payloadjson
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.
Verify and handle callbackjavascript
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.

Local payment callback headershttp
1X-Webhook-Signature: hex_hmac_sha256_of_raw_body2X-Webhook-Event: payment.success
Global webhook headershttp
1X-Paylor-Signature: hex_hmac_sha256_of_raw_body2X-Paylor-Event: global.collection.confirmed
The 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.

StatusMeaning
PENDINGProvider request has been created and is waiting for callback confirmation.
CONFIRMEDCollection 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.
SETTLEDA delayed-settlement collection's window elapsed and funds moved to the available balance (collection.settled webhook).
REQUESTEDWithdrawal request has been created and funds may be held.
PROCESSINGProvider accepted the withdrawal request and is processing payout.
COMPLETEDProvider confirmed payout success.
FAILEDProvider rejected the request or callback reported failure.