Skip to main content

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.

note

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

  1. Define Your Webhook Endpoint:
    Create a dedicated POST endpoint on your server to receive webhook events from Modem Pay. Ensure this endpoint is accessible over HTTPS to maintain security.

  2. 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 the event field, while the main data is within the payload.

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.

  1. Retrieve the Signature:
    In your webhook endpoint, extract the x-modem-signature header from the incoming request.

  2. 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"
tip

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.