Webhooks
Modem Pay's webhooks notify your server about key events, such as customer creation, payment intent updates, and successful charges. To receive and securely process these notifications, follow the steps outlined here for setting up, validating, and handling webhooks.
You can configure a global webhook URL in the Modem Pay dashboard under Developers > Webhooks, to receive all webhook events for your business. Alternatively, you can provide a callback_url parameter in individual API requests (such as when creating a Payment Intent). If you specify a callback_url, Modem Pay will send webhook notifications for that specific transaction to the provided URL, and it will behave the same way as the global webhook. This allows you to override the global webhook on a per-request basis if needed.
Setting Up Webhooks
-
Define Your Webhook Endpoint:
Create a dedicatedPOSTendpoint on your server to receive webhook events from Modem Pay. Ensure this endpoint is accessible over HTTPS to maintain security. -
Listening for Events:
Your endpoint will receive a variety of event notifications (e.g.,payment_intent.created,charge.succeeded). The event type is provided in theeventfield, while the main data is within thepayload.
Webhook Security and Signature Validation
To confirm the integrity of incoming events, Modem Pay includes a unique x-modem-signature header in each webhook request. This header allows you to verify that the event originated from Modem Pay.
-
Retrieve the Signature:
In your webhook endpoint, extract thex-modem-signatureheader from the incoming request. -
Webhook Validation:
Use the following sample TypeScript code to validate the webhook event signature in your endpoint:app.post("/modem-webhook", (req, res) => {
const payload = JSON.stringify(req.body);
const signature = req.headers["x-modem-signature"];
// Ensure the signature is provided
if (!signature) {
return res.status(400).json({ message: "Signature missing" });
}
// Use your webhook signing secret from the Modem Pay dashboard for global webhooks.
// If you are validating a webhook sent to a per-request callback_url, use your API secret key instead.
const secretHash = process.env.MODEM_PAY_SECRET_HASH;
// Generate the HMAC-SHA512 hash for signature comparison
const computedSignature = crypto
.createHmac("sha512", secretHash)
.update(payload)
.digest("hex");
// Ensure the signature length matches to avoid timing attacks
if (computedSignature.length !== signature.length) {
return res.status(400).json({ message: "Invalid signature length" });
}
// Use timing-safe comparison to protect against timing attacks
if (
!crypto.timingSafeEqual(
Buffer.from(computedSignature),
Buffer.from(signature)
)
) {
return res.status(400).json({ message: "Invalid signature!" });
}
// Parse the payload to handle the event
try {
const eventPayload = JSON.parse(payload);
// You can add logic here to process the eventPayload
res.sendStatus(200); // Acknowledge successful receipt
} catch (error) {
res.status(400).json({ message: "Invalid event data" });
}
});
Supported Event Types:
- Payment Intent Events:
"payment_intent.created","payment_intent.cancelled","payment_intent.expired" - Charge Events:
"charge.succeeded","charge.failed","charge.expired","charge.cancelled","charge.created","charge.updated" - Payout Events:
"transfer.succeeded","transfer.cancelled","transfer.reversed","transfer.failed","transfer.flagged"
Always respond to webhook events with an HTTP 200 OK status to confirm receipt. This ensures Modem Pay doesn’t retry the webhook unnecessarily.
Webhook Retries
Modem Pay ensures reliable delivery of webhook events through an automatic retry mechanism.
If your server fails to acknowledge a webhook event with a 200 OK HTTP response, Modem Pay will retry the delivery up to 3 times, with each retry spaced 10 minutes apart. A failure could be due to reasons such as server timeouts, network errors, or non-2xx response codes.
To avoid unnecessary retries and ensure smooth webhook processing, always return a 200 OK response immediately, even if you need to carry out time-consuming tasks (like writing to a database or triggering external API calls). It’s recommended to offload such operations to a background worker or job queue.
By following this best practice, you help prevent duplicate processing and reduce the risk of delays or rate-limiting due to repeated webhook attempts.
Sample Webhook Events
Successful Payment
{
"event": "charge.succeeded",
"payload": {
"id": "23419194-7324-4c2b-a74b-d8fba736e692",
"type": "payment",
"amount": 2500,
"currency": "GMD",
"payment_method": "qmoney",
"customer": "0f6e0912-2f2f-4d8b-bd68-ac8c97c44ae6",
"metadata": {},
"status": "completed",
"payment_link_id": null,
"custom_fields_values": {},
"business_id": "a10a51ae-05c8-406e-968c-7b1d309a77e0",
"account_id": "7788dd90-3801-47a6-8597-b46eaa0a7d7d",
"test_mode": true,
"customer_name": "Zypheron Kade",
"customer_phone": "7024725",
"customer_email": "calebchibuike110@gmail.com",
"createdAt": "2024-12-15T09:34:17.036Z",
"updatedAt": "2024-12-15T09:34:43.306Z",
"payment_account": ".... 9944",
"payment_metadata": {
"os": "MacOS",
"browser": "Firefox",
"ipAddress": "196.223.149.183",
"timestamp": "2024-12-15T09:34:43.266Z",
"deviceType": "Desktop",
"urlIPAddress": "https://db-ip.com/196.223.149.183",
"screenResolution": "1600x900"
},
"payment_intent_id": "273ac751-369d-4198-8340-da480208bded",
"transaction_reference": "VOAO1CFEN7",
"payment_method_id": "4dbaaec5-7a16-4908-8fd9-c3d2bd24f739"
}
}
Cancelled Payment
{
"event": "payment_intent.cancelled",
"payload": {
"source": "online",
"payment_metadata": {},
"type": "payment",
"requires_auth": false,
"auth_length": 4,
"auth_mode": "none",
"edited": false,
"id": "a8299e58-c2db-48d6-9d4d-695ee0b85a94",
"amount": 500,
"currency": "GMD",
"payment_method": null,
"customer": null,
"metadata": {},
"status": "cancelled",
"payment_link_id": null,
"custom_fields_values": {},
"business_id": "120c298f-b736-49c1-8220-8a8c84a5d8f3",
"account_id": "199bf17b-0b5a-45c0-a5b0-1d29f5f173c9",
"test_mode": false,
"customer_name": null,
"customer_phone": null,
"customer_email": null,
"risk_score": {},
"transaction_fee": 0,
"transaction_fee_type": "customer",
"coupon": null,
"discount": null,
"sub_total": null,
"createdAt": "2025-09-10T13:56:25.668Z",
"updatedAt": "2025-09-10T13:56:41.499Z",
"payment_intent_id": "a8299e58-c2db-48d6-9d4d-695ee0b85a94",
"transaction_reference": null,
"reference": null,
"payment_method_id": null,
"payment_account": null,
"failure_reason": null,
"notes": null,
"paid_date": null
}
}
Summary
By following these steps, you can securely receive and process webhook events from Modem Pay, automating actions based on customer interactions and payment flows. Make sure to store and securely handle the event data, as each event may carry critical transaction or customer information relevant to your application.