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.