Docs

Direct Debit · Guides

Penny validation

Confirm a CLABE or debit card belongs to your customer with a MXN $0.01 deposit before you debit it.

Penny validation is an ownership check. It confirms that a Mexican bank account (CLABE) or debit card belongs to the customer you say it belongs to, before you start pulling real money from it.

  1. Monato sends a MXN $0.01 deposit to the account.
  2. Banxico returns a CEP (Comprobante Electrónico de Pago) for that deposit. The CEP includes the name and tax ID of the account holder.
  3. Monato compares the CEP’s beneficiary data with the customer record you provided.
  4. If they match, the account is trusted and direct debits can proceed. If they don’t, the account is rejected.

Penny validation is turned on per organization. Once enabled, it applies automatically to every instrument and charge that qualifies. You don’t toggle it per request.

When it runs

With penny validation enabled for your organization, it runs:

  • when you create an instrument (CLABE or debit card) with a customer_id;
  • when you create a charge that includes both inline_instrument and inline_customer, including each row of a batch file.

It does not run for a charge on a stored instrument_id, which must already be active, or for an inline_instrument sent with a customer_id instead of inline_customer. If the same account was validated before, the earlier result is reused.

Why it matters

Penny validation protects you from:

  • Customers entering the wrong CLABE by mistake.
  • Fraudulent attempts to debit an account the customer doesn’t own.
  • Failed charges, and their fees, on accounts that were never going to work.

Because the check runs before the real charge, a rejected account is never debited.

Before you start: create a customer

Penny validation compares the CEP with a customer record, so create the customer first with POST /customers.

Request
{
  "name": "Jane Doe",
  "document_type": "mx_rfc",
  "document_number": "XXXX000000XXX",
  "email": "jane@example.com",
  "phone_number": "+520000000000"
}
Field Required Description
name Yes Customer’s full legal name
document_type Yes Document type, for example mx_rfc or passport
document_number Yes Document number
email No Customer email
phone_number No Customer phone number
Response 201
{
  "id": "33333333-3333-4333-8333-333333333333",
  "org_id": "00000000-0000-4000-8000-000000000000",
  "name": "Jane Doe",
  "document_type": "mx_rfc",
  "document_number": "XXXX000000XXX",
  "email": "jane@example.com",
  "phone_number": "+520000000000",
  "customer_metadata": null,
  "created_at": "2026-01-15T12:00:00Z",
  "updated_at": null
}

Keep the id. You use it as customer_id on an instrument. For inline charges, send the customer data as inline_customer.

Two ways to trigger it

  1. Create an instrument. Validate once, then reuse the instrument for future charges.
  2. Create a charge with inline_instrument. Validate and charge in a single call.

Both flows use the same match logic and the same billing rule: you only pay for the first successful validation of an account (see Billing).

Option 1: Validate when creating an instrument

Use this flow to save a customer’s bank account for repeat billing. Call POST /instruments.

Request
{
  "customer_id": "33333333-3333-4333-8333-333333333333",
  "type": "mx_clabe",
  "mx_clabe": {
    "clabe": "000000000000000001"
  }
}

The instrument is created right away with status: "verification_in_progress", and the check starts in the background.

Response 201
{
  "id": "66666666-6666-4666-8666-666666666666",
  "org_id": "00000000-0000-4000-8000-000000000000",
  "customer_id": "33333333-3333-4333-8333-333333333333",
  "type": "mx_clabe",
  "status": "verification_in_progress",
  "ownership_verification_result": null,
  "ownership_verification_result_at": null,
  "mx_clabe": {
    "clabe": "000000000000000001",
    "can_credit": true,
    "can_debit": true
  },
  "created_at": "2026-01-15T12:00:00Z",
  "updated_at": null
}

How the instrument status changes

CEP result status ownership_verification_result
Account holder matches the customer active matched
Account holder does not match errored no_match
CEP couldn’t be retrieved errored no_match
Validation error occurred errored errored
The bank reports the account is canceled errored account_canceled
The bank reports the account does not exist errored account_does_not_exist
Still waiting on CEP verification_in_progress null
  • active: safe to use. You can create charges that reference this instrument’s id.
  • errored: the account failed validation. Charges that reference this instrument are rejected (see below).
  • verification_in_progress: the CEP hasn’t arrived yet. Wait for the webhook, or poll the instrument.

Option 2: Validate when creating a charge

Use this flow to validate and charge in one call, for example for a one-off direct debit where you don’t need to store the account. Call POST /charges.

When a charge includes both inline_instrument and inline_customer, penny validation always runs. You can’t skip it in this flow.

Note:

How long it takes depends on whether the account was validated before.

  • First validation of the account: the charge stays in verification_in_progress while Monato waits for the CEP. This is typically under 90 seconds, but can take up to 3 hours in the worst case (see CEP processing timeline).
  • Account already validated: the charge moves to pending or canceled almost instantly.
{
  "amount": 150.00,
  "currency": "mxn",
  "reference": "INV-001",
  "inline_instrument": {
    "type": "mx_clabe",
    "identifier": "000000000000000001"
  },
  "inline_customer": {
    "name": "Jane Doe",
    "document_type": "mx_rfc",
    "document_number": "XXXX000000XXX"
  }
}

How the charge status changes

Penny validation runs before the risk engine. Only charges whose ownership matched go on to the risk engine.

CEP result status ownership_verification_result
Account holder matches the customer pending (ready to process) matched
Account holder does not match canceled no_match
CEP couldn’t be retrieved canceled no_match
Validation error occurred canceled errored
The bank reports the account is canceled canceled account_canceled
The bank reports the account does not exist canceled account_does_not_exist
Still waiting on CEP verification_in_progress null

Trying to charge an un-validated instrument

If you create a charge with an instrument_id that is not active (for example errored after a failed validation, or still verification_in_progress), the API rejects it with 400. Wait for the instrument to become active before you charge it.

{
  "instrument_id": "66666666-6666-4666-8666-666666666666",
  "amount": 150.00,
  "currency": "mxn",
  "reference": "INV-002"
}

Once validation fails, the instrument is a dead end. Ask the customer for a different account and create a new instrument.

Billing

Penny validation is billed per account, not per request.

  • The first time an account is validated successfully, meaning the validation completed with matched or no_match, you are charged for it.
  • Later validations of the same account are free, whether they come from a new instrument or a charge with inline_instrument.
  • A validation that did not complete with matched or no_match (for example, the CEP was never retrieved and it ended as errored) doesn’t count as the billable first validation. The next attempt on that account is billed as the first.

The comparison with the customer still runs every time. An account that matched one customer can come back as no_match against a different customer. Only the billing changes.

Batch charges

Every charge created from a batch file (SFTP or Portal) behaves exactly like a single charge with inline_instrument. The same rules apply to each row:

  • Penny validation always runs for each charge.
  • Penny validation runs first. Only charges whose ownership matched go on to the risk engine.
  • Each charge changes status independently, based on its own CEP result, using the same table as single charges.
  • Billing follows the same per-account rule: you only pay for the first successful validation of each unique account, however many charges in the batch (or across batches) use it.

CEP processing timeline

When an account has never been validated, Monato has to pull the CEP from Banxico. Most complete within 90 seconds, but banks vary. If the CEP isn’t available on the first try, Monato retries automatically over roughly 3 hours.

As soon as an attempt succeeds, the result is locked in as matched or no_match, and the instrument or charge moves on.

If every attempt comes back empty, the validation fails:

  • The instrument is set to errored with no_match.
  • Any associated charges are canceled with no_match.

Once an account has a matched or no_match result, future validations of that account resolve almost instantly.

Webhooks

Monato reports penny validation outcomes by webhook, so you don’t need to poll.

Trigger Event type When it fires
Charge with inline_instrument charge_result Only when the charge is canceled due to no_match or errored
Instrument creation instrument_ownership_verification_result Whenever the instrument transitions to active or errored

For inline charges, successful validations don’t send a dedicated webhook. The charge moves on to normal processing and you get the usual events for it.

{
  "event": "charge_result",
  "timestamp": "2026-01-15T12:01:30.000000+00:00",
  "data": {
    "charge_id": "77777777-7777-4777-8777-777777777777",
    "charge_result": "canceled",
    "charge_reference": "INV-001",
    "amount": 150.0,
    "declined_reason": null,
    "risk_status": "ok",
    "risk_reasons": null,
    "client_debt_id": null,
    "instrument_identifier": null,
    "ownership_verification_result": "no_match",
    "ownership_verification_result_at": "2026-01-15T12:01:29.450000+00:00",
    "ownership_information": {
      "name": "JOHN ROE",
      "document_id": "XXXX000000YYY"
    }
  }
}

ownership_information carries the account holder data from the CEP whatever the result. It is null when no CEP is available. See Webhook events for every field.

Status flows

Created
  → verification_in_progress
      → active           (matched)
      → errored          (no_match / errored / account_canceled / account_does_not_exist)

FAQ

Can I opt a single instrument or charge out of penny validation? No. Once your organization has it enabled, it runs automatically. If you don’t want validation on a specific charge, reference an already-validated instrument_id instead of using inline_instrument.

What if the customer data doesn’t exactly match what the bank has? The comparison tolerates common formatting differences (casing, whitespace, punctuation in names). Material mismatches, such as a different name or a different RFC, come back as no_match and the charge is canceled.

How do I know when validation is done? Listen for the webhooks above, or poll the instrument or charge.