Handling Payment Notifications
Learn how to set up and handle EcobankPay webhook notifications
Important
Notification Options
Once the customer pays or cancels an invoice, depending on your site setup, EcobankPay can do two things:
- Send an email informing you of the payment or cancellation
- Send a notification to an IPN (instant payment notification) URL for your application to take some automated action
How IPN Notifications Work
Here's how the IPN notification process works:
EcobankPay calls your IPN URL
Once an invoice is paid or cancelled, EcobankPay does a GET to your IPN URL with the invoice_id parameter. Suppose your IPN url is
https://ipn_url.ecobankpay.com.gh/notifyand a customer pays or cancels invoice with invoice ID AA123, EcobankPay will perform the GET request:https://ipn_url.ecobankpay.com.gh/notify?invoice_id=AA123This call is meant to prompt your application that an event of interest has occurred with respect to this invoice.
Your application queries transaction status
Your application will then make the GET call:
https://pgw.paywithonline.com/v1/gateway/json_status_chk?invoice_id=AA123&merchant_key=YOUR_MERCHANT_KEYto receive details of the event.
EcobankPay responds with transaction details
EcobankPay responds to the query in the step above with a JSON object. Details of the response object are described as part of the status check endpoint.
Sample Payment Notification Request
Request:
Endpoint:
https://pgw.paywithonline.com/v1/gateway/json_status_chkMethod:
GET| Parameter | Required | Description |
|---|---|---|
| merchant_key | Y | Your unique assigned Merchant Key |
| invoice_id | Y | Invoice Id of the particular transaction whose details to check |
Best Practices for Webhook Handling
- Acknowledge receipt: Your webhook endpoint should return a 2xx HTTP status code as quickly as possible to acknowledge receipt of the webhook notification.
- Process asynchronously: Process the webhook data asynchronously after acknowledging receipt, especially if the processing involves time-consuming operations.
- Implement retry logic: Design your system to handle potential duplicate notifications, as webhook delivery may be retried in case of temporary failures.
- Secure your endpoint: Ensure your webhook endpoint is properly secured, validate the incoming data, and verify that the notification is authentic.
- Handle all transaction status types: Your webhook handler should be designed to process all possible transaction statuses (paid, cancelled, failed, etc.).
- Proper error logging: Implement comprehensive error logging to troubleshoot issues with webhook processing.
Implementing the Webhook Handler
Below is a simplified code example of how you might implement a webhook handler in a Node.js Express application:
const express = require('express');
const axios = require('axios');
const app = express();
// Webhook endpoint to receive EcobankPay notifications
app.get('/api/webhook/ecobankpay', async (req, res) => {
try {
// Get the invoice ID from the request
const { invoice_id } = req.query;
if (!invoice_id) {
return res.status(400).send('Missing invoice_id parameter');
}
// Acknowledge receipt immediately
res.status(200).send('Webhook received');
// Process asynchronously
processPaymentNotification(invoice_id).catch(err => {
console.error('Error processing payment notification:', err);
});
} catch (error) {
console.error('Webhook handler error:', error);
// Still return 200 to acknowledge receipt
if (!res.headersSent) {
res.status(200).send('Webhook received with errors');
}
}
});
async function processPaymentNotification(invoiceId) {
const MERCHANT_KEY = process.env.ECOBANKPAY_MERCHANT_KEY;
// Query EcobankPay for transaction status
const statusUrl = `https://pgw.paywithonline.com/v1/gateway/json_status_chk?invoice_id=${invoiceId}&merchant_key=${MERCHANT_KEY}`;
const response = await axios.get(statusUrl);
const paymentData = response.data;
// Process based on transaction status
switch(paymentData.status) {
case 'paid':
// Update order status, send confirmation email, etc.
await updateOrderStatus(invoiceId, 'paid', paymentData);
break;
case 'cancelled':
// Handle cancelled payment
await updateOrderStatus(invoiceId, 'cancelled', paymentData);
break;
case 'failed':
// Handle failed payment
await updateOrderStatus(invoiceId, 'failed', paymentData);
break;
default:
console.log(`Unhandled payment status: ${paymentData.status} for invoice ${invoiceId}`);
}
}
async function updateOrderStatus(invoiceId, status, paymentData) {
// Your implementation to update order in your database
console.log(`Updated order ${invoiceId} to status: ${status}`);
}
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});