Webhooks

Webhooks

Receive real-time notifications when payment events occur.

How Webhooks Work

  1. 1Configure your webhook URL in the dashboard or per-request
  2. 2When a payment event occurs, we send a POST request to your URL
  3. 3Your server processes the webhook and returns a 200 status
  4. 4If delivery fails, we retry with exponential backoff

Webhook Events

EventDescriptionService
payment.completedPayment was successfulM-PESA, Cards
payment.failedPayment was declined or failedM-PESA, Cards
payment.pendingPayment is awaiting confirmationM-PESA, Cards
payment.cancelledPayment was cancelled by customerM-PESA
payment.timeoutPayment timed outM-PESA
payment.refundedPayment was refundedCards
payout.completedPayout was delivered successfullyPayouts
payout.failedPayout delivery failedPayouts
payout.processingPayout is being processedPayouts

Webhook Payload

All webhooks include these standard fields:

webhook-payload.jsonjson
{
"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

Always verify webhook signatures to ensure requests come from SerixPay and haven't been tampered with.

Each webhook includes an X-SerixPay-Signature header containing an HMAC-SHA256 signature of the request body.

webhook-handler.jsjavascript
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 example
app.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.