A sale paid by bank transfer in Mexico usually closes on an image. A screenshot from the banking app or the receipt PDF arrives, and on that the order ships, the car is handed over or the loan is activated. The image carries what seems to matter: the tracking key, the amount, the sending bank, the destination account and the time.

None of that data is protected. A screenshot can be edited in minutes with any image editor, and a PDF with a PDF editor. The result looks identical to the original. The ways a receipt fails keep repeating:

  • Invented. The tracking key matches no payment.
  • Altered. A real payment with the amount or the destination account changed.
  • Someone else’s. A genuine receipt, but of a payment that went to another person.
  • Reversed. The payment went out, but the receiving bank returned or rejected it.

Checking the bank statement by hand works, but it is slow. Reconciliation usually runs in batches, and by the time it confirms, the order has often shipped.

Why the receipt is not enough

SPEI is the payment system Banxico, Mexico’s central bank, operates. Every transfer between two different institutions goes through it, and the sending bank assigns it a tracking key (clave de rastreo). The receipt that gets shared is generated by the sending bank at the moment of sending. It states what that bank ordered, not what happened next.

What happened next lives in Banxico. A payment can stay in process, settle, be returned because the destination account does not exist or is closed, or be rejected. The app screenshot does not change when the payment is returned. Banxico’s record does.

When a payment settles and the receiving bank confirms the credit, Banxico issues the electronic payment receipt (Comprobante Electrónico de Pago, CEP), with its digital seal. The CEP carries what no screenshot proves: the name and the RFC or CURP of whoever sent and whoever received, both accounts, both banks, the amount and the time of the credit. The receiving bank has a few minutes to confirm, so a freshly settled payment may not have a CEP yet.

Two things are worth knowing before using the record.

  • Banxico finds a payment only when the date, the tracking key or the reference, both banks, the beneficiary account and the amount all match. A mistyped field gives the same result as an invented receipt.
  • A transfer between two accounts at the same bank does not go through SPEI. It has no record in Banxico and no CEP.

How a payment gets confirmed

Confirming a payment means asking Banxico about it with the data on the receipt, and deciding on its answer instead of the image. Each status leads to a different decision.

  1. Settled, with a CEP. The money arrived. Before releasing, compare the CEP’s beneficiary with who you expected. Someone else’s receipt gives itself away here, because the payment exists in another person’s name.
  2. Settled, no CEP yet. The receiving bank has not confirmed. Check again in a few minutes.
  3. In process. Banxico has the payment, but it has not been credited. Wait.
  4. Returned, rejected or cancelled. The money did not stay in the beneficiary’s account. Do not release.
  5. Not found. No payment matches that data. Before concluding the receipt is fake, rule out a typo.

The first filter against that typo is the CLABE, Mexico’s 18-digit bank account number. Its first three digits name the bank and its last digit is a check digit computed from the other 17. A mistyped digit is caught without asking anyone, and the account’s bank comes from its prefix.

The CEP in XML or PDF stays as evidence in the payment’s file. If an operation ends in a dispute, that is the document that counts, not the screenshot.

Example

Singula runs the Banxico lookup in a single call. You send the data on the receipt and, if you want, who you expect as the beneficiary.

curl -X POST https://api.singula.mx/app/spei \
  -H "Authorization: Bearer $SINGULA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "fecha": "2026-10-15",
    "criterio_tipo": "clave_rastreo",
    "criterio": "SINGULA202610150001",
    "emisor": "40012",
    "cuenta": "014180000123456780",
    "monto": "1500.00",
    "expected": { "beneficiario_nombre": "GRUPO SINGULA, S.A. DE C.V." }
  }'

The answer, abridged, carries the status in Banxico, the beneficiary on the receipt and how it compares with what you sent:

{
  "data": {
    "found": true,
    "status": "liquidado",
    "verdict": "confirmed",
    "cep_available": true,
    "beneficiary": {
      "name": "GRUPO SINGULA, S.A. DE C.V.",
      "account": "014180000123456780",
      "bank": "SANTANDER"
    },
    "matches": {
      "beneficiary_name": "match",
      "amount": "match",
      "account": "match"
    }
  },
  "status": "success"
}

The CLABE is checked on its own with the CLABE validator, which carries no charge. With a mistyped digit it answers valid: false, the reason check_digit_mismatch and the digit that belonged there:

curl -X POST https://api.singula.mx/app/clabe/validate \
  -H "Authorization: Bearer $SINGULA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "account": "012180001234567895" }'

What to do now

Create an account and use your test key (sk_test_). The test environment answers with the same shape as production and consumes no balance, so the lookup is wired into your order flow before the first real payment. The full field-by-field reference is in the documentation.