Webhooks
Receive real-time notifications when payment events occur.
How Webhooks Work
- 1Configure your webhook URL in the dashboard or per-request
- 2When a payment event occurs, we send a POST request to your URL
- 3Your server processes the webhook and returns a 200 status
- 4If delivery fails, we retry with exponential backoff
Webhook Events
| Event | Description | Service |
|---|---|---|
payment.completed | Payment was successful | M-PESA, Cards |
payment.failed | Payment was declined or failed | M-PESA, Cards |
payment.pending | Payment is awaiting confirmation | M-PESA, Cards |
payment.cancelled | Payment was cancelled by customer | M-PESA |
payment.timeout | Payment timed out | M-PESA |
payment.refunded | Payment was refunded | Cards |
payout.completed | Payout was delivered successfully | Payouts |
payout.failed | Payout delivery failed | Payouts |
payout.processing | Payout is being processed | Payouts |
Webhook Payload
All webhooks include these standard fields:
{ "event": "payment.completed", "timestamp": "2024-01-15T10:30:00Z", "data": { "orderId": "ORDER-001", "transactionId": "txn_abc123", "amount": 1000, "currency": "KES", "status": "completed", "method": "mpesa", "metadata": { "customField": "value" } }}Signature Verification
Important
Each webhook includes an X-SerixPay-Signature header containing an HMAC-SHA256 signature of the request body.
const crypto = require('crypto');function verifyWebhookSignature(payload, signature, secret) { const expectedSignature = crypto .createHmac('sha256', secret) .update(JSON.stringify(payload)) .digest('hex'); return crypto.timingSafeEqual( Buffer.from(signature), Buffer.from(expectedSignature) );}// Express.js exampleapp.post('/webhook', express.json(), (req, res) => { const signature = req.headers['x-serixpay-signature']; const webhookSecret = process.env.SERIXPAY_WEBHOOK_SECRET; if (!verifyWebhookSignature(req.body, signature, webhookSecret)) { console.error('Invalid webhook signature'); return res.status(401).json({ error: 'Invalid signature' }); } // Process webhook const { event, data } = req.body; switch (event) { case 'payment.completed': // Update order status in your database await updateOrderStatus(data.orderId, 'paid'); break; case 'payment.failed': // Handle failed payment await handleFailedPayment(data.orderId); break; } // Return 200 to acknowledge receipt res.status(200).json({ received: true });});Retry Policy
If your endpoint returns a non-2xx status or times out, we retry with exponential backoff:
Attempt 1
Immediate
Attempt 2
1 min
Attempt 3
5 min
Attempt 4
30 min
Attempt 5
2 hours
After 5 failed attempts, the webhook is marked as failed. You can manually retry from the dashboard.
Best Practices
Return 200 quickly
Acknowledge receipt immediately. Process the webhook asynchronously if needed.
Handle duplicates
Webhooks may be sent multiple times. Use the orderId/transactionId for idempotency.
Verify signatures
Always verify the webhook signature before processing.
Use HTTPS
Ensure your webhook endpoint uses HTTPS for secure communication.
Log webhooks
Log all webhook events for debugging and audit purposes.
Testing Webhooks
Use these tools to test your webhook implementation:
Webhook.site
Get a temporary URL to receive and inspect webhooks during development.
ngrok
Expose your local server to receive webhooks during development.