Webhooks (Callbacks)
Webhooks allow Zikopay to send real-time updates about transaction status changes to your application. Rather than requiring you to poll our API for updates, webhooks push notifications to your specified endpoint whenever a transaction status changes.
Webhook Setup
You configure webhooks on a per-transaction basis by providing a callback_url
parameter when initiating a transaction. This URL should be a publicly accessible endpoint on your server that can receive POST requests.
Webhook Payload
When a transaction status changes, Zikopay sends a POST request to your callback URL with the following payload format:
{
"reference": "TXN174506674372585E",
"external_transaction_id": "dfghjkl",
"status": "completed",
"amount": 100,
"currency": "XAF",
"type": "payin",
"operator": "orange_cm",
"created_at": "2025-04-12"
}
Webhook Parameters
Parameter | Type | Description |
---|---|---|
reference | string | Unique Zikopay transaction reference ID |
external_transaction_id | string | ID from the payment provider's system |
status | string | New transaction status |
amount | number | Transaction amount |
currency | string | Transaction currency |
type | string | Transaction type: payin or payout |
operator | string | Payment operator used |
created_at | string | Date when the transaction was created |
Possible Status Values
Status | Description |
---|---|
pending | Transaction has been initiated but not yet processed |
processing | Transaction is being processed by the payment provider |
completed | Transaction has been successfully completed |
failed | Transaction has failed |
cancelled | Transaction was cancelled by the user or system |
expired | Transaction expired before completion |
refunded | Transaction was refunded |
Handling Webhooks
Your webhook endpoint should respond to Zikopay's POST requests with a 200 OK
status code to acknowledge receipt of the webhook. If your endpoint returns any other status code, Zikopay will consider the delivery failed and will retry the webhook.
Implementation Best Practices
- Verify the webhook source - Check transaction references against your records
- Process idempotently - Handle duplicate webhooks gracefully
- Respond quickly - Process asynchronously and respond with 200 status immediately
- Log everything - Keep detailed logs of all webhook requests
- Handle all statuses - Be prepared to handle any status in the status list
Webhook Retry Logic
If your endpoint fails to return a 200 OK
status code, Zikopay will retry the webhook according to the following schedule:
- 1st retry: 5 minutes after initial failure
- 2nd retry: 30 minutes after initial failure
- 3rd retry: 2 hours after initial failure
- 4th retry: 6 hours after initial failure
- 5th retry: 24 hours after initial failure
After the 5th failed attempt, the webhook will not be retried automatically. You can view failed webhooks in your merchant dashboard and trigger manual retries.
Example Webhook Handler Implementation
PHP (Laravel)
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Transaction;
use Illuminate\Support\Facades\Log;
class WebhookController extends Controller
{
public function handleZikopayWebhook(Request $request)
{
// Log the incoming webhook
Log::info('Zikopay webhook received', $request->all());
// Get the transaction reference
$reference = $request->input('reference');
$status = $request->input('status');
// Find the transaction in your database
$transaction = Transaction::where('reference', $reference)->first();
if (!$transaction) {
Log::warning('Transaction not found for webhook', ['reference' => $reference]);
return response()->json(['status' => 'Transaction not found'], 404);
}
// Update the transaction status
$transaction->status = $status;
$transaction->external_id = $request->input('external_transaction_id');
$transaction->save();
// Process based on status
switch ($status) {
case 'completed':
// Process successful payment
// e.g., confirm order, send confirmation email, etc.
$this->processCompletedPayment($transaction);
break;
case 'failed':
// Handle failed payment
$this->processFailedPayment($transaction);
break;
// Handle other statuses
}
// Always return 200 status to acknowledge webhook receipt
return response()->json(['status' => 'success']);
}
private function processCompletedPayment($transaction)
{
// Your logic to handle completed payments
}
private function processFailedPayment($transaction)
{
// Your logic to handle failed payments
}
}
Node.js (Express)
const express = require('express');
const router = express.Router();
// Webhook handler for Zikopay
router.post('/webhook', async (req, res) => {
try {
console.log('Zikopay webhook received:', req.body);
const { reference, status, external_transaction_id, amount, currency, type } = req.body;
// Find transaction in your database
const transaction = await Transaction.findOne({ reference });
if (!transaction) {
console.warn(`Transaction not found for webhook: ${reference}`);
// Still return 200 to acknowledge receipt
return res.status(200).json({ status: 'Transaction not found' });
}
// Update transaction status
transaction.status = status;
transaction.externalId = external_transaction_id;
transaction.lastUpdated = new Date();
await transaction.save();
// Process based on status
switch (status) {
case 'completed':
// Handle successful payment
await processSuccessfulPayment(transaction);
break;
case 'failed':
// Handle failed payment
await processFailedPayment(transaction);
break;
case 'processing':
// Payment is being processed
break;
// Handle other statuses
}
// Always return 200 status
return res.status(200).json({ status: 'success' });
} catch (error) {
console.error('Error processing webhook:', error);
// Still return 200 to prevent retries if it's our processing that failed
return res.status(200).json({ status: 'error processed' });
}
});
async function processSuccessfulPayment(transaction) {
// Implement your business logic for successful payments
// For example:
// - Update order status
// - Send confirmation email
// - Grant access to purchased content
}
async function processFailedPayment(transaction) {
// Implement your business logic for failed payments
// For example:
// - Notify customer
// - Update order status
// - Trigger retry flow
}
module.exports = router;
Python (Django)
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST
import json
import logging
from myapp.models import Transaction
logger = logging.getLogger(__name__)
@csrf_exempt
@require_POST
def zikopay_webhook(request):
try:
payload = json.loads(request.body)
logger.info(f"Zikopay webhook received: {payload}")
reference = payload.get('reference')
status = payload.get('status')
external_id = payload.get('external_transaction_id')
# Find the transaction in your database
try:
transaction = Transaction.objects.get(reference=reference)
# Update the transaction
transaction.status = status
transaction.external_id = external_id
transaction.save()
# Process based on status
if status == 'completed':
# Handle successful payment
process_completed_payment(transaction)
elif status == 'failed':
# Handle failed payment
process_failed_payment(transaction)
# Handle other statuses
except Transaction.DoesNotExist:
logger.warning(f"Transaction not found for webhook: {reference}")
# Still return 200 to acknowledge receipt
return JsonResponse({'status': 'Transaction not found'}, status=200)
# Always return 200 OK to acknowledge receipt
return JsonResponse({'status': 'success'}, status=200)
except json.JSONDecodeError:
logger.error("Invalid JSON in webhook payload")
return JsonResponse({'status': 'Invalid payload'}, status=200)
except Exception as e:
logger.exception(f"Error processing webhook: {str(e)}")
# Still return 200 to acknowledge receipt
return JsonResponse({'status': 'error processed'}, status=200)
def process_completed_payment(transaction):
# Implement your business logic for successful payments
# For example:
# - Update order status
# - Send confirmation email
# - Grant access to purchased content
pass
def process_failed_payment(transaction):
# Implement your business logic for failed payments
# For example:
# - Notify customer
# - Update order status
# - Trigger retry flow
pass
Webhook Security
To ensure the security of your webhook endpoint, we recommend the following practices:
1. Validate Webhooks
Although Zikopay doesn't currently provide signed webhooks, you should always validate webhook data by:
- Checking that the transaction reference exists in your system
- Verifying that the amount and currency match your records
- Checking that status transitions make sense (e.g., a transaction shouldn't go from 'failed' to 'completed')
2. Use HTTPS
Always use HTTPS for your webhook URL to ensure the data is encrypted in transit.
3. IP Whitelisting
If possible, configure your server firewall to only accept webhook POST requests from Zikopay's IP addresses. Contact support for the current list of IPs.
4. Implement Rate Limiting
Protect your webhook endpoint from potential abuse by implementing rate limiting.
5. Process Asynchronously
Process webhook data asynchronously to:
- Return a 200 response quickly to avoid retries
- Prevent timeouts during complex processing
- Ensure resilience if your backend systems are slow or temporarily unavailable
Common Webhook Scenarios
Payment Completion
When a customer completes a payment, your webhook will receive a payload with status: "completed"
. Your system should:
- Update the order status
- Send confirmation emails
- Provision the purchased product or service
- Update your internal accounting records
Payment Failure
When a payment fails, your webhook will receive a payload with status: "failed"
. Your system should:
- Update the order status
- Notify the customer
- Possibly offer alternative payment methods
- Update any pending inventory holds
Payment Cancellation
When a payment is cancelled, your webhook will receive a payload with status: "cancelled"
. Your system should:
- Update the order status
- Release any held inventory
- Notify the customer if necessary
- Update your sales pipeline
Troubleshooting Webhooks
Webhook Not Received
If your system isn't receiving webhooks:
- Check that the callback URL provided in the transaction request is correct and publicly accessible
- Verify that your server isn't blocking incoming requests
- Check your web server logs for any errors
- Verify that you're handling the POST request correctly
Processing Errors
If your webhook handler is throwing errors:
- Make sure you're returning a 200 status code even when you encounter processing errors
- Log the full webhook payload for debugging
- Implement proper error handling and fallbacks
Duplicate Webhooks
Occasionally, you may receive the same webhook multiple times. Always implement idempotent processing to handle this case gracefully:
- Check if you've already processed this exact status update
- Use database transactions to prevent duplicate changes
- Design your business logic to be safe for repeated execution
Testing Webhooks
During development and testing, you can use the test environment to receive webhook notifications:
- Use a service like Webhook.site or ReqBin to receive and inspect webhooks
- Set up a development endpoint that logs all incoming webhooks without processing them
- Use ngrok or similar tunneling services to expose localhost to the internet for testing
To generate test webhooks, create transactions in the Zikopay test environment with different test parameters.
Webhook Logging and Monitoring
We recommend implementing comprehensive logging and monitoring for your webhook handlers:
- Log all incoming webhooks with timestamps and full payloads
- Alert on webhook processing failures
- Monitor webhook processing times
- Track the distribution of different webhook statuses
- Reconcile processed webhooks with your transaction records regularly
This approach will help you quickly identify and resolve any issues with your webhook processing.