Integrating Account-on-file Payments

Overview

To initiate Account-on-file payments, the customer must first authorise a consent with their bank. This authorisation is handled through Lean LinkSDK, which provides a secure, user-friendly interface for reviewing consent details and confirming approval.

Once the consent is authorised, it acts as a long-lived authorisation. Your application can then initiate multiple payments programmatically, within the approved limits, without requiring the customer to re-authorise each payment.

How it works

The Account-on-file payment flow consists of the following steps:

  1. Create an Account-on-file consent that defines the limits, duration, and scope for future payments.
  2. Start the consent authorisation flow using Lean LinkSDK.
  3. The customer selects their bank, reviews the consent details, and authorises the consent.
  4. Handle the customer returning to your application using preconfigured redirect URLs.
  5. Use captureRedirect() to retrieve and display the consent authorisation result screen.
  6. Initiate payments programmatically using server-to-server API calls, within the scope of the authorised consent.
  7. Listen for consent and payment state changes using webhooks.

Creating consent

Use the Account-on-file API to create a new long-lived consent.

A consent defines the customer, destination account, validity period, and limits that apply to all payments initiated under it. You can control how long the consent remains valid using expiration_date_time. If not specified, consents are valid for up to 12 months by default.

Provide the following identifiers:

  • customer_id the customer granting the consent (debtor)
  • destination_account_id the account receiving funds (creditor)

Define payment limits and reset period using control_parameters.


📘

Understanding Control Parameters

Read more about consent control parameters (limits) in a dedicated section.


curl -L 'https://sandbox.leantech.me/consents/account-on-file' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer {{api_token}}' \
-d '{
    "customer_id": "<your_customer_id>",
    "destination_account_id": "<your_destination_account_id>",
		"expiration_date_time": "2026-09-24T14:15:22Z",
    "currency": "AED",
    "reference": "ACME-ACCOUNT-LINK-2025-001",
    "purpose": "GDS",
    "control_parameters": {
        "period_type": "Day",
        "max_individual_amount": 5000.00
    }
}'
{
  "id": "2a4ab530-9e92-4dd4-bfbb-c43a3c4d3207",
  "application_id": "43f1eafb-2551-4af5-b7e1-87ca7aa6508c",
  "start_date_time": "2025-10-24T14:15:22Z",
  "expiration_date_time": "2026-09-24T14:15:22Z",
  "customer_id": "26b69ba5-0447-45b6-be7d-376d6b432843",
  "status": "AWAITING_AUTHORISATION",
  "destination_account_id": "26438bf6-0d33-4f11-a025-a9d5294be012",
  "currency": "AED",
  "control_parameters": {
    "type": "VariableOnDemand",
    "period_type": "Day",
    "period_start_date": "2025-10-24T14:15:22Z",
    "max_individual_amount": 5000.00,
    "max_cumulative_amount": null,
    "max_cumulative_number_of_payments": null,
    "max_cumulative_amount_per_period": null,
    "max_cumulative_number_of_payments_per_period": null
  },
  "purpose": "GDS",
  "reference": "ACME-ACCOUNT-LINK-2025-001"
}
❗️

Persist the returned consent id. This value is required for all subsequent operations involving the consent, including authorisation, payment initiation and consent management.


Once created, the consent enters the AWAITING_AUTHORISATION state. It remains in this state until the customer completes the authorisation flow using Lean LinkSDK and approves the consent with their bank.


Triggering consent authorisation

Use the LinkSDK authorizeConsent() function, to start the consent authorisation flow.

Pass the consent id returned from the previous step as a consent_id parameter.

LeanV2.authorizeConsent({
    sandbox: true,
    app_token: "<your_application_id>",
    consent_id: "<your_consent_id>>",
    success_redirect_url: "<url_to_redirect_on_success>",
    fail_redirect_url: "<url_to_redirect_on_failure>",
    access_token: "<customer_scoped_access_token>",
    risk_details: {} // omitted for brevity 
})

📘

Risk Details Collection

Open Finance regulations require the collection of risk-related metadata to help prevent fraud. The risk_details object allows you to provide this information. See Risk Details for more information.


Bank selection, consent details and redirection

In the consent authorisation process, LinkSDK guides the customer through:

  • Selecting their bank
  • Reviewing consent details and limits
  • Redirecting to their bank to authorise the consent

These steps are fully handled by LinkSDK and use CBUAE certified customer-facing screens.


📘

Screen Customisation

Due to Open Finance regulations, customer-facing screens in payment flow, must be certified and offer limited customisation.


Consent authorisation flow

During the authorisation the customer is being handed over from your application to the bank where they can perform consent authorisation. The customer will return to your application through a redirect URL passed as a parameter to the authorizeConsent function.


📘

Using custom bank selection

Use Create your own bank list guide, if you want to provide a custom banks selection screen. Pass resulting bank_identifier to the authorizeConsent function to skip the default bank selection.


After successful authorisation, the consent transitions to the AUTHORISED state and can be used to initiate payments within the defined control parameters.

Use consent.status.updated webhook to receive asynchronous notifications about consent state


Capturing redirects and displaying the authorisation result page

After the authorisation flow completes, Lean redirects the customer back to your configured redirect URL, appending query parameters that describe the consent outcome.

Capture Redirect Screen


To display the final result screen, call captureRedirect() and pass the parameters received in the redirect:

LeanSDK.captureRedirect({
    app_token: "<your_app_id>",
    bank_identifier: "<from_query_param>",
    consent_id:"<from_query_param>",
    consent_attempt_id: "<from_query_param>",
    customer_id: "<from_query_param>",
    granular_status_code: "<from_query_param>",
    status_additional_info: "<from_query_param>",
    sandbox: true,
    access_token: "<customer_scoped_access_token>"   
})

Initiating a payment

Once a consent is authorised, your backend can initiate payments on behalf of the customer using the Account on File API.


📘

Idempotency

The API uses the Idempotency-Key header to ensure that payment initiation requests are processed exactly once. Use a unique key for each intended payment.

If the same request is retried with the same Idempotency-Key, Lean does not create a duplicate. Instead, the API returns the payment resource generated during the initial request.


curl -L 'https://sandbox.leantech.me/payments/account-on-file' \
-H 'idempotency-key: KG5LxwFBepaKHyUD' \
-H 'Content-Type: application/json' \
-H 'Authorization: ••••••' \
-d '{
    "consent_id": "2a4ab530-9e92-4dd4-bfbb-c43a3c4d3207",
    "amount": 130.52,
    "purpose": "FIS",
    "reference": "ACME-ACCOUNT-LINK-2025-001",
    "creditor_reference": "INV-2025-004321",
    "risk_details": {}
}'
{
  "id": "341c3308-408d-48d9-ae44-c98eb1aa9a6f",
  "status": "CREATED",
  "consent_id": "123e4567-e89b-12d3-a456-426655440000",
  "amount": {
    "currency": "AED",
    "value": 130.52
  },
  "reference": "ACME-ACCOUNT-LINK-2025-001"
}

Each payment must reference an authorised consent_id and comply with the limits defined in the consent's control parameters. Payment initiation is performed server-to-server using your API credentials and does not have to involve any customer interaction.


Listening to state changes

Lean emits webhook events for both consent and payment state changes.


{
  "type": "payment.created",
  "payload": {
    "id": "f672849b-b4f1-46b1-99cd-80e0bd24c6c0",
    "customer_id": "CUSTOMER_ID_RELATED_TO_PAYMENT",
    "intent_id": "INTENT_ID_RELATED_TO_PAYMENT",
    "status": "PENDING_WITH_BANK",
    "amount": 12.34,
    "currency": "AED",
    "iso_status": "PENDING",
    "payment_destination_id": "DESTINATION_ID_RELATED_TO_PAYMENT"
  },
  "message": "A payment object has been created.",
  "timestamp": "2025-12-17T13:59:32.008767568Z",
  "event_id": "7d1e3de6-fb8b-4480-8652-903c75b8b8c8"
}
{
  "type": "payment.updated",
  "payload": {
    "id": "f672849b-b4f1-46b1-99cd-80e0bd24c6c0",
    "customer_id": "CUSTOMER_ID_RELATED_TO_PAYMENT",
    "intent_id": "INTENT_ID_RELATED_TO_PAYMENT",
    "status": "PENDING_WITH_BANK",
    "amount": 12.34,
    "currency": "AED",
    "bank_transaction_reference": "528b2b3c-128a-4b51-b50e-31cfc6d7e01b",
    "iso_status": "ACCEPTED_SETTLEMENT_COMPLETED",
    "payment_destination_id": "DESTINATION_ID_RELATED_TO_PAYMENT"
  },
  "message": "A payment object has been updated.",
  "timestamp": "2025-12-17T14:00:00.731572845Z",
  "event_id": "bb93b16a-f1cd-486d-887e-b63492b4ebdd"
}
{
  "event_id": "cce42859-055b-4271-b4c0-b94d54a3b894",
  "type": "consent.status.updated",
  "message": "A payment consent has been updated.",
  "timestamp": "2025-10-07T10:32:39.691593783Z",
  "payload": {
    "id": "f8668fb8-62fa-4dae-b8a6-ac6a5d5a88eb",
    "status": "SUSPENDED",
    "customer_id": "69b33353-79f8-42ca-9bfe-a3d742cc353c",
    "account_id": "69b33353-79f8-42ca-9bfe-a3d742cc353c",
    "consent_type": "PAYMENT",
    "application_id": "69b33353-79f8-42ca-9bfe-a3d742cc353c",
    "updated_at": "2025-10-07T14:17:34.342847Z",
  }
}

Configure webhook subscriptions in the Webhooks section of the Application Dashboard.


Webhooks panel in Application Dashboard