> ## Documentation Index
> Fetch the complete documentation index at: https://docs.fintoc.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Accept direct debit payments in Mexico

> Enroll a customer's CLABE account or debit card under a direct debit mandate, then charge it automatically for subscriptions or on-demand payments.

Direct Debit in Mexico (domiciliación bancaria) lets you charge your customers' bank accounts and debit cards automatically. The customer authorizes a mandate once. After Fintoc approves the mandate, you can charge the account on a recurring schedule or on demand without further customer action.

Unlike other payment methods, direct debit has an **asynchronous approval step**: after enrollment, the mandate stays in review for about one business day. The `payment_method` becomes chargeable only when the mandate is approved.

You can enroll a payment method in two ways:

1. **By API (merchant-hosted):** you collect your customer's account details and consent documents in your own UI and send them to Fintoc in a single request.
2. **Through the Fintoc-hosted checkout:** you create a Checkout Session and redirect your customer, and Fintoc handles the full enrollment flow, including identity validation and the mandate document.

Accepting direct debit payments takes four steps:

1. **Create a Customer**, using your Secret Key
2. **Enroll the payment method**, by API or through a Checkout Session
3. **Handle mandate approval events** through webhooks
4. **Charge the enrolled account**, with subscription invoices or on-demand charges

## Before you begin

Direct Debit is available for organizations operating in Mexico. Before integrating, make sure you have:

* A Fintoc account with Direct Debit Mexico enabled
* A [Secret Key and Public Key](/guides/home/api-keys)
* A webhook endpoint to receive mandate and payment events

All direct debit operations use `MXN`.

## How the mandate works

Every enrollment creates a **mandate**: the customer's authorization for you to charge their account. The mandate defines:

| Concept      | Description                                                                                                                                                                        |
| ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `max_amount` | The maximum amount you can charge per billing period, in MXN cents. This is what the customer authorizes.                                                                          |
| `interval`   | The billing period of the authorization: `week`, `month`, or `year`.                                                                                                               |
| `status`     | `pending` (waiting approval), `active` (chargeable), `rejected` (refused and never chargeable), `expired` (no confirmation arrived in time), or `canceled` (no longer chargeable). |

Fintoc enforces the mandate limit on every charge. If a charge would exceed `max_amount` for the current period, Fintoc rejects the charge with `limit_exceeded` before the charge reaches the customer's bank.

## Step 1: Create a Customer

Every direct debit payment method belongs to a `Customer`. For direct debit, the customer needs a full name, an email, and a Mexican tax ID (`mx_rfc`).

<CodeGroup>
  ```bash cURL theme={null}
  curl --request POST "https://api.fintoc.com/v2/customers" \
    --header 'Authorization: YOUR_TEST_SECRET_KEY' \
    --header 'Content-Type: application/json' \
    --data-raw '{
      "name": "Felipe Castro",
      "email": "felipe@example.com",
      "tax_id": {
        "type": "mx_rfc",
        "value": "AAAA010101AAA"
      },
      "metadata": {}
    }'
  ```

  ```javascript Node theme={null}
  const { Fintoc } = require('fintoc');

  const fintoc = new Fintoc('YOUR_TEST_SECRET_KEY');

  const customer = await fintoc.v2.customers.create({
    name: 'Felipe Castro',
    email: 'felipe@example.com',
    tax_id: {
      type: 'mx_rfc',
      value: 'AAAA010101AAA',
    },
    metadata: {},
  });
  ```

  ```python Python theme={null}
  from fintoc import Fintoc

  client = Fintoc('YOUR_TEST_SECRET_KEY')

  customer = client.v2.customers.create(
      name='Felipe Castro',
      email='felipe@example.com',
      tax_id={
          'type': 'mx_rfc',
          'value': 'AAAA010101AAA',
      },
      metadata={},
  )
  ```

  ```json Response theme={null}
  {
    "id": "cus_NffrFeUfNV2Hib",
    "object": "customer",
    "created_at": "2026-08-10T15:22:11.474Z",
    "mode": "test",
    "name": "Felipe Castro",
    "email": "felipe@example.com",
    "tax_id": {
      "type": "mx_rfc",
      "value": "AAAA010101AAA"
    },
    "metadata": {}
  }
  ```
</CodeGroup>

Fintoc returns the created `Customer`. Store the `id` to enroll a `payment_method` for it in the next step.

## Step 2, Option 1: Enroll by API (merchant-hosted)

Use this option when you collect the account details and consent documents in your own UI. Send the account details, authorized mandate limit, and four consent documents as files in a single `multipart/form-data` request:

<CodeGroup>
  ```bash cURL theme={null}
  curl --request POST "https://api.fintoc.com/v2/payment_methods" \
    --header 'Authorization: YOUR_TEST_SECRET_KEY' \
    --form "type=mx_direct_debit" \
    --form "customer=cus_NffrFeUfNV2Hib" \
    --form "mx_direct_debit[account_type]=clabe" \
    --form "mx_direct_debit[account_number]=000000000000000000" \
    --form "mx_direct_debit[max_amount]=35000" \
    --form "mx_direct_debit[interval]=month" \
    --form "mx_direct_debit[consent_documents][contract]=@signed_contract.pdf" \
    --form "mx_direct_debit[consent_documents][selfie]=@selfie.jpg" \
    --form "mx_direct_debit[consent_documents][id_front]=@ine_front.jpg" \
    --form "mx_direct_debit[consent_documents][id_back]=@ine_back.jpg"
  ```

  ```javascript Node theme={null}
  const fs = require('fs');

  const paymentMethod = await fintoc.v2.paymentMethods.create({
    type: 'mx_direct_debit',
    customer: 'cus_NffrFeUfNV2Hib',
    mx_direct_debit: {
      account_type: 'clabe',
      account_number: '000000000000000000',
      max_amount: 35000,
      interval: 'month',
      consent_documents: {
        contract: fs.createReadStream('signed_contract.pdf'),
        selfie: fs.createReadStream('selfie.jpg'),
        id_front: fs.createReadStream('ine_front.jpg'),
        id_back: fs.createReadStream('ine_back.jpg'),
      },
    },
  });
  ```

  ```python Python theme={null}
  with open('signed_contract.pdf', 'rb') as contract, \
       open('selfie.jpg', 'rb') as selfie, \
       open('ine_front.jpg', 'rb') as id_front, \
       open('ine_back.jpg', 'rb') as id_back:
      payment_method = client.v2.payment_methods.create(
          type='mx_direct_debit',
          customer='cus_NffrFeUfNV2Hib',
          mx_direct_debit={
              'account_type': 'clabe',
              'account_number': '000000000000000000',
              'max_amount': 35000,
              'interval': 'month',
              'consent_documents': {
                  'contract': contract,
                  'selfie': selfie,
                  'id_front': id_front,
                  'id_back': id_back,
              },
          },
      )
  ```

  ```json Response theme={null}
  {
    "id": "pm_NffrFeUfNV2Hib",
    "object": "payment_method",
    "created_at": "2026-08-10T15:22:11.474Z",
    "customer": "cus_NffrFeUfNV2Hib",
    "mode": "test",
    "mx_direct_debit": {
      "account_type": "clabe",
      "institution_id": "mx_banco_santander",
      "interval": "month",
      "last_four_digits": "0000",
      "max_amount_cents": 35000,
      "max_amount_currency": "MXN",
      "status": "pending"
    },
    "type": "mx_direct_debit"
  }
  ```
</CodeGroup>

The merchant-hosted enrollment request accepts these parameters:

| Param                                                    | Type    | Required    | Description                                                                                                                                                                   |
| -------------------------------------------------------- | ------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type`                                                   | string  | Yes         | `mx_direct_debit`.                                                                                                                                                            |
| `customer`                                               | string  | Yes         | ID of the `Customer`. Must have a full name, a `tax_id` of type `mx_rfc`, and an `email`.                                                                                     |
| `mx_direct_debit.account_type`                           | string  | Yes         | `clabe` or `debit_card`.                                                                                                                                                      |
| `mx_direct_debit.account_number`                         | string  | Yes         | For `clabe`, the 18 digits of the standardized Mexican bank account number (CLABE). For `debit_card`, the 16 digits of the card number, which must pass the Luhn check.       |
| `mx_direct_debit.institution_id`                         | string  | Conditional | Institution that issued the account. Required when `account_type` is `debit_card`. Ignored for `clabe`, where the first three digits of the account number identify the bank. |
| `mx_direct_debit.max_amount`                             | integer | Yes         | Maximum amount per billing period, in MXN cents. Must match the amount authorized in the signed contract.                                                                     |
| `mx_direct_debit.interval`                               | string  | Yes         | Billing period of the authorization: `week`, `month`, or `year`.                                                                                                              |
| `mx_direct_debit.consent_documents.contract`             | string  | Yes         | Signed authorization contract, following the official mandate format. Binary file (`multipart/form-data`): JPEG, PNG, or PDF, between 100 bytes and 10 MB.                    |
| `mx_direct_debit.consent_documents.selfie`               | string  | Yes         | Selfie of the customer, ideally holding their National Electoral Institute ID (INE). Binary file (`multipart/form-data`): JPEG, PNG, or PDF, between 100 bytes and 10 MB.     |
| `mx_direct_debit.consent_documents.id_front` / `id_back` | string  | Yes         | Front and back of the customer's INE. Binary file (`multipart/form-data`): JPEG, PNG, or PDF, between 100 bytes and 10 MB.                                                    |
| `metadata`                                               | object  | No          | Key-value object for merchant-provided data.                                                                                                                                  |

Fintoc returns the created `payment_method` with the mandate already in review. See the [Create a payment method reference](/api/payments-api/payment-methods/payment-methods-create) for every parameter and error the endpoint returns.

The request is atomic. If a document is missing or a file has an invalid format or size, Fintoc returns `422 Unprocessable Entity` and creates no `payment_method`.

<Warning>
  A `payment_method` cannot be edited after creation. If the consent documents are rejected or incomplete, Fintoc cancels the method (`payment_method.canceled`) and you must **create a new `payment_method`** with the corrected documents.
</Warning>

## Step 2, Option 2: Enroll through the Fintoc-hosted checkout

Use this option to let Fintoc handle the full enrollment flow. Your customer completes their personal data, enters their CLABE or debit card, authorizes the mandate, and completes identity validation on the Fintoc-hosted page. Fintoc generates the signed mandate document for you.

The hosted checkout supports two flows:

* `flow: subscription` enrolls the payment method **and** starts a recurring subscription in one step. Fintoc schedules and charges the invoices of each billing cycle automatically.
* `flow: setup` **only enrolls the payment method, with no scheduled charges**. Use it when you want to save the account and decide later when and how much to charge (on-demand invoices, see Step 4).

Create a `Checkout Session` with `flow: subscription` to enroll and start a recurring subscription in one step:

<CodeGroup>
  ```bash cURL theme={null}
  curl --request POST "https://api.fintoc.com/v2/checkout_sessions" \
    --header 'Authorization: YOUR_TEST_SECRET_KEY' \
    --header 'Content-Type: application/json' \
    --data-raw '{
      "flow": "subscription",
      "amount": 35000,
      "currency": "MXN",
      "success_url": "https://merchant.com/success",
      "cancel_url": "https://merchant.com/cancel",
      "payment_method_types": ["mx_direct_debit"],
      "customer_data": {
        "tax_id": {
          "type": "mx_rfc",
          "value": "AAAA010101AAA"
        },
        "name": "Felipe Castro",
        "email": "felipe@example.com",
        "metadata": {}
      },
      "line_items": [
        {
          "price_data": {
            "currency": "MXN",
            "unit_amount": 35000,
            "product_data": {
              "name": "Plan 1"
            },
            "recurring": {
              "interval": "month",
              "interval_count": 1
            }
          },
          "quantity": 1
        }
      ],
      "metadata": {}
    }'
  ```

  ```javascript Node theme={null}
  const checkoutSession = await fintoc.v2.checkoutSessions.create({
    flow: 'subscription',
    amount: 35000,
    currency: 'MXN',
    success_url: 'https://merchant.com/success',
    cancel_url: 'https://merchant.com/cancel',
    payment_method_types: ['mx_direct_debit'],
    customer_data: {
      tax_id: { type: 'mx_rfc', value: 'AAAA010101AAA' },
      name: 'Felipe Castro',
      email: 'felipe@example.com',
      metadata: {},
    },
    line_items: [
      {
        price_data: {
          currency: 'MXN',
          unit_amount: 35000,
          product_data: { name: 'Plan 1' },
          recurring: { interval: 'month', interval_count: 1 },
        },
        quantity: 1,
      },
    ],
    metadata: {},
  });
  ```

  ```python Python theme={null}
  checkout_session = client.v2.checkout_sessions.create(
      flow='subscription',
      amount=35000,
      currency='MXN',
      success_url='https://merchant.com/success',
      cancel_url='https://merchant.com/cancel',
      payment_method_types=['mx_direct_debit'],
      customer_data={
          'tax_id': {'type': 'mx_rfc', 'value': 'AAAA010101AAA'},
          'name': 'Felipe Castro',
          'email': 'felipe@example.com',
          'metadata': {},
      },
      line_items=[
          {
              'price_data': {
                  'currency': 'MXN',
                  'unit_amount': 35000,
                  'product_data': {'name': 'Plan 1'},
                  'recurring': {'interval': 'month', 'interval_count': 1},
              },
              'quantity': 1,
          },
      ],
      metadata={},
  )
  ```

  ```json Response theme={null}
  {
    "id": "cs_JiFtR3vBhK5nQm7",
    "object": "checkout_session",
    "flow": "subscription",
    "amount": 35000,
    "currency": "MXN",
    "status": "created",
    "redirect_url": "https://pay.fintoc.com/checkout/cs_JiFtR3vBhK5nQm7",
    "success_url": "https://merchant.com/success",
    "cancel_url": "https://merchant.com/cancel",
    "payment_method_types": ["mx_direct_debit"],
    "customer": "cus_NffrFeUfNV2Hib",
    "subscription": "sub_L2pXm4KnQ7bYwR9",
    "payment_method": null,
    "metadata": {}
  }
  ```
</CodeGroup>

Fintoc returns the created `Checkout Session`. Use its `redirect_url` to send your customer to the hosted checkout.

With `flow` set to `subscription`, Fintoc derives the mandate's `max_amount` and `interval` from `line_items`: `unit_amount` multiplied by `quantity`, and `recurring.interval`.

To create the payment method with no scheduled charges, set `flow` to `setup`. `setup` sessions have no `line_items` to define the maximum amount, but you can send the mandate limit in `payment_method_options`.

If you omit `payment_method_options`, Fintoc creates the mandate with a default limit of MXN 10,000 per month (`max_amount: 1000000`, `interval: "month"`).

```json theme={null}
{
  "flow": "setup",
  "currency": "MXN",
  "payment_method_types": ["mx_direct_debit"],
  "payment_method_options": {
    "mx_direct_debit": {
      "max_amount": 500000,
      "interval": "month"
    }
  }
}
```

Redirect your customer to the session's `redirect_url`. On the hosted page, the customer completes five steps:

1. Personal data: name, Mexican Federal Taxpayer Registry number (RFC), and email, pre-filled from `customer_data`.
2. Account enrollment: CLABE or debit card.
3. Mandate authorization: full mandate document.
4. Identity validation: selfie and INE.
5. Confirmation.

<Info>
  After the checkout finishes, the enrollment is **not** complete: the mandate stays in review and `mx_direct_debit.status` remains `pending`. Don't activate your customer's service until you receive `payment_method.activated`.
</Info>

## Step 3: Handle mandate approval events

Fintoc reviews the mandate asynchronously and sends the approval about **one business day** after enrollment. Always use webhooks to track the outcome:

| Event                       | When                                                                                                                                                             | Recommended action                                     |
| --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ |
| `checkout_session.finished` | The hosted checkout finished. Includes the `payment_method` with `mx_direct_debit.status` as `pending` and, for `subscription`, the subscription (`incomplete`). | Store the IDs. Don't activate the service yet.         |
| `checkout_session.expired`  | The session expired before completion.                                                                                                                           | Offer the customer a new attempt.                      |
| `payment_method.activated`  | The mandate was approved. `mx_direct_debit.status` is now `active` and the method is chargeable.                                                                 | Activate the service and start charging.               |
| `payment_method.canceled`   | The mandate was rejected, the documents were incomplete, the mandate expired without confirmation, or the method was revoked.                                    | Enroll the customer again with a new `payment_method`. |

When the mandate is approved for a `subscription` flow, the subscription becomes `active` and Fintoc generates and charges the first `invoice` automatically.

## Step 4: Charge the enrolled account

With `mx_direct_debit.status` in `active`, you can charge the `payment_method` in two ways:

* **Subscription:** Fintoc generates an `invoice` for each billing cycle and collects the invoice automatically against the enrolled account. Handle the `invoice.payment_succeeded` and `invoice.payment_failed` events.
* **On-demand:** You create a one-off `invoice` associated with the payment method.

<CodeGroup>
  ```bash cURL theme={null}
  curl --request POST "https://api.fintoc.com/v2/invoices" \
    --header 'Authorization: YOUR_TEST_SECRET_KEY' \
    --header 'Content-Type: application/json' \
    --data-raw '{
      "customer": "cus_NffrFeUfNV2Hib",
      "default_payment_method": "pm_NffrFeUfNV2Hib",
      "lines": [
        {
          "amount": 15000,
          "currency": "MXN",
          "name": "Monthly service",
          "quantity": 1
        }
      ],
      "metadata": {
        "order_id": "order_98765"
      }
    }'
  ```

  ```javascript Node theme={null}
  const invoice = await fintoc.v2.invoices.create({
    customer: 'cus_NffrFeUfNV2Hib',
    default_payment_method: 'pm_NffrFeUfNV2Hib',
    lines: [
      {
        amount: 15000,
        currency: 'MXN',
        name: 'Monthly service',
        quantity: 1,
      },
    ],
    metadata: { order_id: 'order_98765' },
  });
  ```

  ```python Python theme={null}
  invoice = client.v2.invoices.create(
      customer='cus_NffrFeUfNV2Hib',
      default_payment_method='pm_NffrFeUfNV2Hib',
      lines=[
          {
              'amount': 15000,
              'currency': 'MXN',
              'name': 'Monthly service',
              'quantity': 1,
          },
      ],
      metadata={'order_id': 'order_98765'},
  )
  ```

  ```json Response theme={null}
  {
    "id": "inv_2bVdWxLpzXq8RkNcM3JtUv9AhTe",
    "object": "invoice",
    "attempt_count": 0,
    "collection_method": "charge_automatically",
    "created_at": "2026-08-10T15:22:11.474Z",
    "currency": "MXN",
    "customer": "cus_NffrFeUfNV2Hib",
    "default_payment_method": "pm_NffrFeUfNV2Hib",
    "external_payment": false,
    "hosted_invoice_url": null,
    "lines": [
      {
        "id": "il_2bVdX0PqJs6RhNu9FmZlTo2EyKq",
        "object": "line_item",
        "name": "Monthly service",
        "description": null,
        "amount": 15000,
        "currency": "MXN",
        "period_end": null,
        "period_start": null,
        "quantity": 1
      }
    ],
    "metadata": { "order_id": "order_98765" },
    "mode": "test",
    "next_payment_attempt_at": null,
    "payments": [],
    "status": "draft",
    "subscription": null,
    "total": 15000
  }
  ```
</CodeGroup>

Fintoc returns the created `invoice` in its initial `draft` state. Once you finalize the invoice, Fintoc charges it automatically against `default_payment_method`. Track the outcome with `invoice.payment_succeeded` or `invoice.payment_failed` (sent along with `payment_intent.succeeded` or `payment_intent.failed`).

<Info>
  **Mandate limit enforcement.** Before Fintoc executes a charge, Fintoc sums the charge amount with any amount already charged or in flight in the current period. Fintoc then compares that total against the mandate's `max_amount`. Fintoc rejects charges over the limit with `422 limit_exceeded` before the charge reaches the customer's bank. Failed charges do not consume the limit. Usage resets at the start of each period.
</Info>

## Rules and validations

Use these validation errors to handle direct debit-specific failures:

| Code                      | Meaning                                                                                                               |
| ------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| `payment_method_inactive` | `mx_direct_debit.status` is `pending`, `rejected`, `expired`, or `canceled`. Only an `active` mandate can be charged. |
| `limit_exceeded`          | The charge would exceed the mandate's `max_amount` for the current period.                                            |
| `422` on session creation | `mx_direct_debit` with a currency other than `MXN`, or the organization doesn't have Direct Debit enabled.            |
| `422` on method creation  | A consent document is missing or has an invalid format or size. The request is atomic: nothing is created.            |

## Test your integration

Using your `test` mode secret key, you can simulate the full direct debit flow without moving money. In `test` mode, Fintoc approves or rejects the mandate in about **1 minute** instead of one business day. A mandate created with a CLABE ending in `8888` never receives bank confirmation, and Fintoc expires the mandate after 7 days.

**CLABE (18 digits).** Only the last four digits act as the trigger. The examples use Santander, and any supported bank behaves the same way:

| CLABE                | Result                                                                                                                                                                                                                                              |
| -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `014180000000041111` | Mandate **rejected**: Fintoc sends `payment_method.canceled` and `mx_direct_debit.status` becomes `rejected` \~1 minute after enrollment.                                                                                                           |
| `014180000000082222` | **In-flow failure with retry** (hosted checkout only): identity validation fails on the first attempt and lets your customer retry.                                                                                                                 |
| `014180000000028888` | Mandate **pending, then expired**: `mx_direct_debit.status` stays `pending` while no confirmation arrives from the bank. After 7 days, Fintoc expires the mandate, sends `payment_method.canceled`, and `mx_direct_debit.status` becomes `expired`. |

To get an approved mandate, use any CLABE that does not end in one of those triggers, such as `014180000000014821`.

**Debit card (16 digits).** The card number controls the mandate outcome:

| Card number        | Result                                                                                                                                    |
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `4111111111111111` | Mandate **approved**: `mx_direct_debit.status` becomes `active` \~1 minute after enrollment.                                              |
| `4574441215190335` | Mandate **rejected**: Fintoc sends `payment_method.canceled` and `mx_direct_debit.status` becomes `rejected` \~1 minute after enrollment. |

**Charges.** The amount controls the outcome, and the result arrives by webhook 5 to 15 minutes after the charge:

| Amount (MXN cents) | Result      |
| ------------------ | ----------- |
| `>= 500`           | `succeeded` |
| `< 500`            | `failed`    |

<Info>
  In `live` mode the identity validation is real and can reject your customer. Make sure your integration handles the `pending` state and the `payment_method.canceled` event before going live.
</Info>
