Skip to main content

Webhooks

E-mandate operations on RecovaPRO are asynchronous. A mandate is created instantly, but the payer's bank decides whether to honour it (up to 24 hours for micro-deposit authorization, and up to 48 hours for e-signature), and its status keeps changing over the lifetime of the direct debit arrangement. Rather than polling Verify mandate, CreditChek submits a POST request to the webhook URL configured for your RecovaPRO profile each time the state of a mandate changes, so your platform can trigger the matching workflow — activate a loan, notify a payer, close out a repayment schedule.

Your endpoint should respond to webhooks as quickly as possible. To acknowledge receipt of a webhook, your endpoint must return a 2xx HTTP status code. This status code should only indicate receipt of the message, not an acknowledgement that it was successfully processed by your system. Any other information returned in the response headers or response body is ignored.

Configure your webhook URL

RecovaPRO events are only dispatched once Webhook settings for RecovaPRO events have been configured on your merchant profile in the CreditChek SaaS Portal. See Integration options.

Security​

All webhook requests are sent with an x-auth-signature header for verification. It should match the secret key pair of the app secret key you used when creating the mandate. See Webhooks for the shared verification and retry behaviour that applies across all CreditChek services.

Webhook structure​

Every RecovaPRO webhook follows the same envelope as the rest of the CreditChek platform:

FieldTypeDescription
eventstringThe event that determines the structure of data.
dataobjectThe mandate payload for the event.

Mandate lifecycle​

The events below map onto the lifecycle of a single e-mandate. Use mandateId (returned as _id on mandate_created_event) or your own reference to correlate them with the mandate on your side:

mandate_created_event      → status: pending      (awaiting the payer's bank)
↓
mandate_approved_event → active: true (mandate placed on the payer's bank record; collection can begin)
↓
mandate_paused_event → status: paused (auto-collection suspended, mandate still valid)
↓
mandate_canceled_event → status: deactivated (terminated by the merchant, final)
mandate_expire_event → status: closed (endDate reached, final)
Ordering

Webhooks are not guaranteed to arrive in order, and a mandate can move to paused and back any number of times before it is closed or deactivated. Treat each event as a state update keyed on mandateId, and ignore an event that describes a state you have already moved past.

Supported events​

Sent as soon as the e-mandate request is created and submitted for authorization. The mandate arrives with status: "pending" — no debit can be attempted against it yet. The _id in this payload is the mandateId referenced by every subsequent event and by the mandate API endpoints.

Sample webhook for a created mandate
{
"event": "mandate_created_event",
"data": {
"businessId": "730c8be89121212121212121",
"borrowerId": null,
"appId": "730c8be89131cd1111111111",
"reference": "563315af-f0a4-420f-8e5e-df9b1c28973b",
"productId": "768fc9999999999999999999",
"bvn": "22357999900",
"startDate": "2024-07-07",
"endDate": "2024-10-31",
"frequency": "monthly",
"totalAmountDue": 15000,
"collectionMode": "auto",
"rcNumber": null,
"corporateAccount": false,
"businessName": null,
"businessEmail": null,
"businessPhone": null,
"debitType": "recurring",
"mandateType": "variable",
"status": "pending",
"balanceEnquiry": false,
"creditInsurance": false,
"consents": [],
"acceptedTerms": true,
"isDemo": true,
"_id": "77192739b3cf770000000000",
"createdAt": "2024-10-23T16:41:29.085Z",
"updatedAt": "2024-10-23T16:41:29.085Z",
"__v": 0,
"outstandingBalance": 15000,
"collectedAmount": 0,
"authorizationType": "e-signature"
}
}

Payload fields

FieldTypeDescription
_idstringThe mandate ID. This is the mandateId used in all other events and API calls.
referencestringUnique reference for the mandate arrangement. Use it to correlate the mandate with the record on your platform.
businessIdstringYour CreditChek business ID.
appIdstringThe app the mandate was created under.
borrowerIdstring | nullThe borrower the mandate belongs to, where the payer already exists on your CreditChek borrower record.
productIdstringThe product the e-mandate is associated with on your merchant profile.
bvnstringBVN of the payer the mandate is placed against.
startDatestringDate (YYYY-MM-DD) the direct debit arrangement becomes collectable.
endDatestringDate (YYYY-MM-DD) the arrangement lapses. Reaching this date triggers mandate_expire_event.
frequencystringCollection frequency for recurring debits — daily, weekly, or monthly. Not applicable to one-time debits.
totalAmountDuenumberTotal amount collectable over the lifetime of the mandate.
outstandingBalancenumberAmount still to be recovered. Equal to totalAmountDue at creation.
collectedAmountnumberAmount recovered so far. 0 at creation.
collectionModestringauto for scheduler-driven collection, or manual where you trigger each debit yourself.
debitTypestringone-time or recurring.
mandateTypestringfixed or variable. See Categories of e-Mandates.
authorizationTypestringHow the payer authorized the mandate — e-signature or micro-deposit.
statusstringpending at creation.
corporateAccountbooleantrue where the payer is a corporate account. rcNumber, businessName, businessEmail, and businessPhone are then set.
balanceEnquirybooleanWhether balance enquiry is enabled on the mandate.
creditInsurancebooleanWhether the ERM credit-risk add-on is attached to the mandate.
acceptedTermsbooleanWhether the payer accepted the direct debit terms.
isDemobooleantrue for mandates created with test credentials. Always confirm this is false before acting on an event in production.

Mandate service events​

EventMandate stateDescription
mandate_created_eventpendingThe e-mandate has been created and submitted to the payer's bank for authorization. Not yet collectable.
mandate_approved_eventactiveThe payer's bank honoured the request and placed the mandate on the account. Collection can begin.
mandate_paused_eventpausedAuto-collection has been suspended on an active mandate. Reversible with a reinstate.
mandate_canceled_eventdeactivatedThe mandate was terminated by the merchant before its end date. Final.
mandate_expire_eventclosedThe mandate reached its endDate and was closed. Final.

Handling mandate events​

Nodejs Example
const secret = process.env.CREDITCHEK_SECRET_KEY;

function verifyWebhook(req, res, next) {
if (req.headers["x-auth-signature"] !== secret) {
return res.status(401).json({
message: "Unauthorized request.",
});
}
next();
}

router.post("/recova/webhook", verifyWebhook, (req, res) => {
// Acknowledge first, process afterwards.
res.sendStatus(200);

const { event, data } = req.body;
const mandateId = data.mandateId || data._id;

switch (event) {
case "mandate_created_event":
// Mandate submitted for authorization — keep the payer waiting on `pending`.
break;
case "mandate_approved_event":
// Mandate is live on data.accountNumber — activate the loan or subscription.
break;
case "mandate_paused_event":
// Collection suspended — pause your repayment schedule.
break;
case "mandate_canceled_event":
case "mandate_expire_event":
// Final states — close out the schedule and stop expecting collections.
break;
}
});
Idempotency

The same event may reach you more than once, for example when a failed delivery is re-sent from your dashboard audit logs. Key your handler on mandateId and the resulting state so that reprocessing an event is harmless.

Sample data

The values in these payloads are illustrative. Use the field names and nesting as the integration contract, and treat the values as mandate-specific.

Contact [email protected] if an expected event does not reach your webhook URL.