DirectPay Webhook Events
Last updated June 10th, 2026
Overview
Our DirectPay webhook events are fired based on the following triggers:
- Successful payment webhook event
- Failed payment webhook event
- Cancelled payment webhook event
- Abandoned payment webhook event
Webhook Events
These are the webhook events sent during the lifecycle of a DirectPay transaction. We have itemised them down below:
- Account connected event
- Payment successful event
- Payment failed event
- Payment cancelled event
- Payment abandoned event
a. Account Connected event (mono.events.account_connected):
After a user successfully connects their bank account during the payment process, this event is triggered. Using the Account ID here, you can fetch customer details like Name, BVN, Account Number, and much more via our Account Identity API.
Request
{
"event": "mono.events.account_connected",
"data": {
"id": "611d575feef5d3371ca9d0d8"
}
}
b. Payment Successful event (direct_debit.payment_successful):
This event is triggered when the one-time debit transaction is successfully completed. With the transaction reference returned here, you can verify the payment status using the Verify Payment Status API.
Request
{
"event": "direct_debit.payment_successful",
"event_id": "PsmZW6jiY6vDuDHeFmvsiJudamnPHuKhAKyoMFPznWs",
"timestamp": "2026-01-07T10:10:50.186Z",
"data": {
"type": "onetime-debit",
"object": {
"_id": "65aa7939564a694c67789012",
"id": "txd_wzb8uhew4j9ngru43a123456",
"status": "successful",
"message": "Payment was successful",
"description": "new payment",
"amount": 20000,
"fee": 6100,
"currency": "NGN",
"liveMode": true,
"account": {
"status": "AVAILABLE",
"linked": true,
"_id": "65aa7935564a694c67123456",
"name": "SAMUEL OLAMIDE",
"accountNumber": "0123456789",
"currency": "NGN",
"balance": 50000,
"type": "SAVINGS ACCOUNT",
"bvn": null,
"authMethod": "mobile_banking",
"liveMode": true,
"app": "61e3798cbbe2010771123456",
"institution": {
"_id": "5f2d08c060b92e2888287707",
"name": "First Bank",
"bankCode": "011",
"type": "PERSONAL_BANKING",
"icon": "https://mono-public-bucket.s3.eu-west-2.amazonaws.com/images/first-bank-icon.png"
},
"scope": [
"payments"
],
"created_at": "2021-07-18T18:54:23.491Z",
"updated_at": "2021-07-18T18:55:16.055Z"
},
"reference": "123456789012",
"verified": true,
"business": "60cc8f95ba1772018c123456",
"created_at": "2021-08-18T18:54:23.491Z",
"updated_at": "2021-08-18T18:55:16.055Z",
"method": "transfer",
"flagged": false,
"flag_reasons": null,
"held_settlement": false
}
}
}
c. Payment Failed event (direct_debit.payment_failed):
This event is sent when there is a bank failure, or network switch downtime after the user has authorized the transaction.
Request
{
"event": "direct_debit.payment_failed",
"event_id": "PsmZW6jiY6vDuDHeFmvsiJudamnPHuKhAKyoMFPznWs",
"timestamp": "2026-01-07T10:10:50.186Z",
"data": {
"type": "onetime-debit",
"object": {
"description": "Wallet Funding",
"amount": 50000,
"fee": 4500,
"liveMode": true,
"customer": null,
"account": "618d41d61846d1e2",
"currency": "NGN",
"reference": "wp_f4719afff5234d60c0570e",
"status": "failed",
"id": "txd_XzhFBoknIjuosOz4",
"created_at": "2024-05-04T17:48:33.627Z",
"updated_at": "2024-05-04T17:49:06.211Z"
}
}
}
d. Payment Cancelled event (direct_debit.payment_cancelled):
This event is sent when the user closes the DirectPay widget/modal without completing the authorization process.
Request
{
"event": "direct_debit.payment_cancelled",
"event_id": "PsmZW6jiY6vDuDHeFmvsiJudamnPHuKhAKyoMFPznWs",
"timestamp": "2026-01-07T10:10:50.186Z",
"data": {
"type": "onetime-debit",
"object": {
"id": "txd_ExP1X0llzmr",
"status": "cancelled",
"message": "This payment was cancelled by the user",
"amount": 20000,
"description": "description of payment",
"fee": 100,
"currency": "NGN",
"account": "66352371c793ad4f8",
"reference": "GP4S6J1424165",
"liveMode": true,
"verified": false,
"meta": null,
"created_at": "2024-05-03T17:48:33.627Z",
"updated_at": "2024-05-03T17:49:06.211Z"
}
}
}
e. Payment Abandoned event (direct_debit.payment_abandoned):
This event is sent when a user disregards the payment widget or the payment link session has timed out.
Request
{
"event": "direct_debit.payment_abandoned",
"event_id": "PsmZW6jiY6vDuDHeFmvsiJudamnPHuKhAKyoMFPznWs",
"timestamp": "2026-01-07T10:10:50.186Z",
"data": {
"type": "onetime-debit",
"object": {
"_id": "662fae55648be1af8",
"id": "txdruhpmxvfo6060k",
"status": "abandoned",
"message": "User abandoned the payment page.",
"amount": 20000,
"description": "description of payment",
"fee": 100,
"currency": "NGN",
"account": "662fae53ff648be1ae4",
"customer": null,
"reference": "ZRDWCG1714385608212",
"liveMode": true,
"verified": false,
"business": "60cc8f95bc5c6b1d",
"meta": null,
"created_at": "2024-04-29T14:27:33.851Z",
"updated_at": "2024-04-29T14:45:00.035Z"
}
}
}
Handling duplicate webhook deliveries and verification
To secure your webhook endpoint, verify the request by checking the mono-webhook-secret header against the secret key configured on your Mono dashboard.
Additionally, Mono may send the same webhook event more than once due to network retries or system redundancy. To prevent duplicate processing of the same event, always check the event_id field before processing webhooks. Store processed event IDs and skip any events you've already handled.
Example implementation (Node.js)
Request
const express = require('express');
const app = express();
// Store processed event IDs (use a database in production)
const processedEvents = new Set();
app.post('/webhooks/mono', express.json(), (req, res) => {
// 1. Verify Webhook Secret
const secret = process.env.MONO_WEBHOOK_SEC;
if (!secret || req.headers['mono-webhook-secret'] !== secret) {
return res.status(401).json({ message: 'Unauthorized request.' });
}
const { event, event_id, data } = req.body;
// 2. Check if already processed
if (processedEvents.has(event_id)) {
return res.status(200).json({ message: 'Already processed' });
}
// Process the webhook
console.log(`Processing ${event} with ID ${event_id}`);
// Your business logic here
if (event === 'direct_debit.payment_successful') {
// Handle successful debit
console.log(`Debit successful: ${data.object.reference}`);
}
// Mark as processed
processedEvents.add(event_id);
res.status(200).json({ message: 'Webhook received' });
});
Webhook Retry behavior
Mono uses an exponential backoff system to retry failed webhook deliveries. When a webhook delivery fails (returns a non-200 HTTP status code), Mono will automatically retry the delivery with the following behavior:
Initial retry delay: 30 seconds after the first failure
Backoff multiplier: ×2 (each retry delay doubles the previous delay)
Retry delay sequence: 30 seconds, 1 minute, 2 minutes, 4 minutes, 8 minutes, 16 minutes, 32 minutes, 1 hour, and so on
Maximum delay cap: 4 hours between retry attempts
Total retry attempts: 25 attempts
Maximum retry window: 48 hours from the initial delivery attempt
HTTP status codes that trigger retries: Any non-200 status code (including 4xx and 5xx errors)
Event ID consistency: The same
event_idis reused across all retry attempts for a given webhook event. This unique identifier ensures that each webhook delivery can be tracked and helps partners easily detect and prevent duplicate notifications in their systems. It also helps us enforce the requirement that the same event does not get delivered twice to the same webhook.
