Una cuenta por cobrar (receivable) es un adeudo con fecha de vencimiento
que Winal convierte automáticamente en un Payment Link de un
solo uso: crea la cuenta, mándale al cliente el link (o el link de WhatsApp que Winal arma
por ti), y cuando la pague, la cuenta se marca paid sola — no hay que
conciliar nada a mano.
Crea una cuenta por cobrar
concepto es el campo obligatorio que describe el adeudo (aparece en el link de
pago y en el mensaje de WhatsApp). Los cuatro datos fiscales del receptor
(customer_rfc, customer_uso_cfdi, customer_regimen_fiscal,
customer_cp) son opcionales, pero van juntos o ninguno: si los das, Winal
intenta timbrar el CFDI automáticamente en cuanto la cuenta se paga.
curl -s https://api.winal.com.mx/v1/receivables \
-H "Authorization: Bearer $SK" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"customer_name": "María López",
"customer_email": "maria.lopez@example.mx",
"customer_phone": "5215512345678",
"concepto": "Mensualidad julio 2026 - plan Pro",
"amount_minor": 150000,
"currency": "MXN",
"due_date": "2026-07-20T00:00:00Z"
}'
{
"id": "0ec348fb-2341-4f31-b870-9cc4d3087b29",
"object": "receivable",
"customer_name": "María López",
"customer_email": "maria.lopez@example.mx",
"customer_phone": "5215512345678",
"amount_minor": 150000,
"currency": "MXN",
"concepto": "Mensualidad julio 2026 - plan Pro",
"due_date": "2026-07-20T00:00:00+00:00",
"status": "open",
"payment_link_id": "27011835-fd92-44dd-8d6c-549c00536703",
"payment_link_url": "/pay/QOECDPCH2HC",
"created_at": "2026-07-08T02:19:53.743499+00:00"
}
payment_link_url es una ruta relativa — antepón el host de tu Winal
(https://winal.com.mx{payment_link_url}) para armar la URL completa que le
mandas al cliente. status es derivado: open → paid (al
cobrarse) o overdue (pasado due_date sin pagar); también puede quedar
canceled.
El link de WhatsApp, listo para mandar
curl -s https://api.winal.com.mx/v1/receivables/0ec348fb-.../whatsapp_link \
-H "Authorization: Bearer $SK"
{
"url": "https://wa.me/5215512345678?text=Hola%20Mar%C3%ADa%20L%C3%B3pez%2C%20tienes%20un%20adeudo%20de%201500.00%20MXN%20por%20concepto%20de%20%22Mensualidad%20julio%202026%20-%20plan%20Pro%22%20con%20vencimiento%2020%2F07%2F2026.%20Puedes%20pagarlo%20aqu%C3%AD%3A%20%2Fpay%2FQOECDPCH2HC"
}
Es una URL https://wa.me/... lista para abrir directo (botón, link en un correo,
etc.) — el mensaje ya trae el monto en pesos, el concepto y la ruta del link de pago
pre-armados. Requiere que la cuenta tenga customer_phone; si no,
400 receivable.no_phone.
El pago marca la cuenta sola
Cuando el cliente paga el link (POST /public/payment_links/{slug}/intents +
confirm — ver Referencia de API), el intent nace
con metadata.payment_link_id apuntando al link de la cuenta. Al llegar
payment_intent.succeeded, un handler del Worker (el mismo tópico que despacha
webhooks) revisa esa metadata: si pertenece a un receivable, lo marca
paid — de forma idempotente, nunca dos veces por reentregas del evento — y, si
la cuenta trae los cuatro datos fiscales completos, dispara el timbrado automático del CFDI.
{
"id": "0ec348fb-2341-4f31-b870-9cc4d3087b29",
"object": "receivable",
"customer_name": "María López",
"...": "...",
"status": "paid",
"paid_at": "2026-07-08T02:21:05.20706+00:00",
"paid_payment_intent_id": "c252d2af-18b3-4fd3-8296-81d2c560db02",
"created_at": "2026-07-08T02:19:53.743499+00:00"
}
paid igual
(el cobro sí sucedió) pero con cfdi_error visible en la respuesta — nunca se
reintenta solo ni se esconde el error.
No hay que hacer nada para que esto ocurra: es automático en cuanto confirmas el pago del link, sea desde el checkout hosteado o desde tu propia integración.
Recordatorios automáticos
Un scheduler del Worker revisa cada 15 minutos qué cuentas tienen un recordatorio pendiente (offsets configurables en el portal, p. ej. antes y después del vencimiento) y manda un correo por el SMTP que configures en portal → Cobranza → Recordatorios — reentrante e idempotente: un ciclo de más, o uno perdido por un reinicio, nunca duplica ni pierde un recordatorio. El link de WhatsApp de arriba es la vía manual complementaria cuando prefieres mandarlo tú mismo.
Aging: antigüedad de saldos
GET /v1/reports/aging agrupa por cliente el saldo vencido en los cuatro cubos
contables estándar (0-30, 31-60, 61-90, 90+ días), para priorizar cobranza.
curl -s https://api.winal.com.mx/v1/reports/aging -H "Authorization: Bearer $SK"
{
"object": "list",
"data": [
{
"customer_email": "aging@prueba.mx",
"customer_name": "Prueba Aging",
"currency": "MXN",
"bucket0_to30_minor": 30000,
"bucket31_to60_minor": 0,
"bucket61_to90_minor": 0,
"bucket90_plus_minor": 0,
"total_minor": 30000
}
]
}
Nota los nombres reales de los campos del bucket — no llevan guión bajo entre el número y la palabra to (bucket0_to30_minor, no bucket_0_to_30_minor).
Estado de cuenta de un cliente
curl -s "https://api.winal.com.mx/v1/receivables/statement?customer_email=maria.lopez@example.mx" \
-H "Authorization: Bearer $SK"
{
"customer_email": "maria.lopez@example.mx",
"receivables": [ { "...": "mismo shape del receivable" } ],
"total_open_minor": 150000,
"total_overdue_minor": 0,
"total_paid_minor": 0
}
Endpoints
| Método | Ruta | Notas |
|---|---|---|
| POST | /v1/receivables | Crea la cuenta y, por dentro, un Payment Link de un solo uso. |
| GET | /v1/receivables | Lista las cuentas del tenant. |
| GET | /v1/receivables/{id} | 404 receivable.not_found si no existe. |
| GET | /v1/receivables/{id}/whatsapp_link | Requiere customer_phone capturado. |
| GET | /v1/receivables/statement?customer_email= | Estado de cuenta agregado de un cliente. |
| GET | /v1/reports/aging | Antigüedad de saldos por cliente, en 4 cubos. |
Errores de receivables
| HTTP | code | Causa |
|---|---|---|
400 | receivable.missing_fields | Falta customer_name, customer_email o concepto. |
400 | receivable.invalid_amount | amount_minor no es positivo. |
400 | receivable.invalid_currency | currency no es ISO 4217 válida. |
400 | receivable.invalid_date | due_date inválida. |
400 | receivable.incomplete_fiscal_receptor | Se mandó solo alguno de los cuatro datos fiscales — van juntos o ninguno. |
404 | receivable.not_found | El id no existe. |
400 | receivable.no_phone | Se pidió whatsapp_link sin customer_phone capturado. |
Ver el envelope completo de error en Errores.