CEP (SPEI receipts)

The Comprobante Electrónico de Pago (CEP) is Banco de México's official receipt for a SPEI interbank transfer — the authoritative proof a transfer settled, signed by Banxico.

Send the transfer data and we return the official receipt synchronously — no credentials of your own required. The SPEI bank codes are derived from each account's CLABE automatically.

Timing (read this first)

A CEP is not always available the moment a transfer happens:

  • It is generated ~30 minutes after the transfer settles. Query before that and you get 409 not_available — this is expected; just retry later.
  • It is retained for ~45 business days, then deleted from Banxico (after which you get 404 not_found). Fetch and store it before then.

Look up a CEP

POST /v1/cep

Parties are nested as debtor (the payer / ordenante) and creditor (the payee / beneficiario) — the same shape used for payments elsewhere in the API.

{
  "date": "2026-06-15",
  "tracking_key": "ABCD1234567890ABCD12",
  "amount": 15000,
  "debtor":   { "account": "012180001234567890" },
  "creditor": { "account": "014180009876543210" }
}

You may pass an explicit bank_code inside debtor/creditor to override the CLABE-derived SPEI code. Response (200 OK) — same debtor/creditor shape, plus the PDF as base64:

{
  "tracking_key": "ABCD1234567890ABCD12",
  "amount": "15000.00",
  "operation_date": "2026-06-16",
  "debtor":   { "name": "Acme Servicios SA de CV", "account": "012180001234567890" },
  "creditor": { "name": "María López Hernández", "account": "014180009876543210" },
  "pdf": "JVBERi0xLjcK..."
}

Responses

Every non-2xx response carries a reason so you can branch on it directly (the message is human-readable, the reason is stable):

Status reason What happened What you should do
200 — CEP retrieved Use pdf + the parsed fields
409 not_available The transfer exists but Banxico hasn't generated the CEP yet (it's < ~30 min since settlement) Retry later — same request will succeed once Banxico publishes it
404 not_found No CEP matches these details, or the transfer is older than ~45 business days Verify the data / tracking_key; if it's an old transfer, it's gone from Banxico
429 rate_limited Banxico throttled the request Back off and retry
502 upstream_error Banxico returned something unexpected Retry; if it persists, contact support
422 — (Laravel errors) Your request failed validation (missing/invalid fields) Fix the request body

Example (409):

{
  "message": "CEP is not available yet. Banxico generates it shortly after settlement.",
  "reason": "not_available"
}

The dashboard exposes the same lookup as a form at /dashboard/cep.

Stored CEPs (per payment)

When you retrieve a CEP for a payment from the dashboard, it is archived (PDF kept in storage even after Banxico's 45-day window) and exposed read-only:

GET /v1/payments/{payment_id}/cep — the stored CepDocument (404 if none):

{
  "payment_id": "...",
  "tracking_key": "ABCD1234567890ABCD12",
  "amount": "15000.00",
  "operation_date": "2026-06-16",
  "debtor":   { "name": "Acme Servicios SA de CV", "account": "012180001234567890" },
  "creditor": { "name": "María López Hernández", "account": "014180009876543210" },
  "pdf_available": true
}

GET /v1/payments/{payment_id}/cep/download — the PDF as base64:

{ "data": "JVBERi0xLjcK...", "mime_type": "application/pdf", "filename": "cep_<tracking_key>.pdf", "encoding": "base64" }

CEP retrieval is synchronous — the result comes back in the response, so there is no CEP webhook. Webhooks are only for asynchronous SAT extractions.