Registro de cambios
Cambios relevantes de la API pública de Coufai.
2026-10-20 — Boleto: entrega solo con la línea digitable
- En algunas cuentas el boleto se entrega solo con la línea digitable, sin PDF:
bankSlipUrlquedanullde forma definitiva — muestra laidentificationFieldal pagador. - Una vez confirmada una emisión en verificación, el reenvío con la misma
externalRefdevuelve el boleto (200,"idempotent": true).
2026-10-15 — Boleto: datos completos del pagador y emisión en verificación
- Nuevo objeto opcional
customer.address(street,number,complement,district,city,zipCode,state) enPOST /v1/boleto. Algunas cuentas pasan a exigir e-mail, teléfono y dirección del pagador: en ellas, si falta o está mal uno de esos campos, la respuesta es400indicando cuál, sin crear el cobro, y el vencimiento debe ser hoy o una fecha futura. En las demás cuentas la dirección se ignora — recomendamos enviarla siempre. - Justo después de la emisión,
bankSlipUrleidentificationFieldpueden venirnullmientras el banco concluye el registro (normalmente menos de un minuto); consulta la transacción enseguida. - Emisión en verificación: cuando la emisión no se confirma, la API responde
400con"Boleto em verificação: a emissão não confirmou. NÃO reenvie — consulte a transação <id> em instantes.". Reenviar la mismaexternalRefrepite esa respuesta hasta la confirmación, en lugar de emitir un segundo boleto. - Una falla pasajera en la que no se emitió nada pasa a responder
503con"Boleto temporariamente indisponível. Tente novamente em instantes."— puedes reintentar. Un error inesperado sigue siendo500(la documentación decía502por error).
2026-09-23 — Boleto: rechazo de la cuenta separado del rechazo de datos
- Cuando el rechazo es de la cuenta (tope de emisión alcanzado o emisión bloqueada), la API deja de responder
"Dados recusados pelo provedor: dados da cobrança inválidos. Corrija e reenvie. (invalid_action)"y pasa a responder400con"Limite de emissão de cobranças da conta atingido. Não reenvie; fale com o suporte. (issuance_limit)"o"Emissão não permitida para esta conta no momento. Não reenvie; fale com o suporte. (account_restricted)". - Con estos dos códigos, reintentar no lo resuelve: no reintente en bucle, contacte a soporte. Los rechazos de datos (
invalid_cpfCnpj,invalid_dueDate…) siguen iguales.
2026-09-12 — Tarifa por boleto no pagado
- Las cuentas con tarifa por boleto no pagado pasan a ser cobradas cuando un boleto vence sin pago. El
transaction.expiredde un boleto trae el nuevo campo opcionalunpaidFeeCents(omitido cuando no hay tarifa), y el cobro aparece enGET /v1/transactionscomo una líneatype: "fee"(feeKind: "boleto_unpaid",relatedTransactionId= el boleto). - Un boleto pagado después del vencimiento devuelve la tarifa automáticamente (línea
type: "fee_reversal"). - El saldo puede quedar negativo por la tarifa; los retiros se rechazan por saldo insuficiente hasta que el siguiente crédito lo cubra.
GET /v1/transactionsacepta?type=feey?type=fee_reversalen el filtro.
2026-09-12 — Boleto: rechazo de datos ahora es un 400 claro; el teléfono nunca bloquea
- Cuando el proveedor rechaza un dato del pagador o del cobro, la API responde
400con"Dados recusados pelo provedor: <campo> inválido. Corrija e reenvie. (<código>)"(antes era un500genérico).<campo>indica qué corregir y<código>es estable (ej.invalid_cpfCnpj): use el código en su lógica, no el texto. customer.phonees opcional: 11 dígitos = celular, 10 = fijo. Si el proveedor rechaza el teléfono, el boleto se emite sin él; un teléfono inválido ya no hace fallar el cobro.
2026-09-06 — Boleto bancario vía API
Nuevo endpoint POST /v1/boleto: emite un boleto bancario (Brasil) y devuelve el enlace público del PDF (bankSlipUrl, servido por Coufai) y la línea digitable (identificationField). Vencimiento opcional (dueDate, por defecto hoy + 3 días); misma idempotencia por externalRef que PIX.
- Mismo ciclo que los cobros PIX:
pending→paid(transaction.paid, connetCentsya descontada la tarifa de boleto de la cuenta) oexpired(transaction.expired) tras el vencimiento. El campomethodviene como"boleto". - Habilitado por cuenta: sin habilitación, la llamada devuelve
400"Boleto não está habilitado para esta conta.". Contacta al soporte. - Los pagos cuyo aviso se retrasó se reconcilian con el banco en segundo plano y se acreditan de todos modos — reacciona al webhook en lugar de hacer polling agresivo.
2026-08-27 — Motivo del rechazo en retiros
Los retiros rechazados ahora informan por qué fallaron. Los eventos transfer.failed, transfer.rejected y transfer.cancelled — y GET /v1/transactions/{id} — pasan a traer dos campos opcionales:
refusedCode— código estable en mayúsculas (ej.:INSUFFICIENT_BALANCE,DAILY_LIMIT_EXCEEDED,INVALID_PIX_KEY). Es el campo para tu lógica: no parsees el texto.refusedReason— descripción legible, hoy devuelta en portugués y sujeta a cambios.
Antes, un retiro rechazado quedaba en failed sin ninguna indicación de causa, y la única salida era reintentar a ciegas. Con el código puedes distinguir lo que vale la pena reintentar (INSUFFICIENT_BALANCE, en minutos) de lo que no sirve sin corrección (INVALID_PIX_KEY) o solo al día siguiente (DAILY_LIMIT_EXCEEDED).
Compatible con lo existente: los campos se omiten cuando el motivo no está disponible, y ningún campo cambió de nombre o tipo. Trata la ausencia como “motivo no informado” — nunca como éxito — y un código desconocido como rechazo genérico, ya que la lista puede crecer.
Un retiro rechazado no se debita: si el importe llegó a salir de tu saldo, se devuelve antes de enviar el evento.
Consulta Motivo del rechazo.
2026-06-04 — Registro de destinatario tercero vía API
Nuevo endpoint POST /v1/recipients para enviar destinatarios terceros (CPF/CNPJ distinto del titular) directamente por la API — antes, el registro solo se hacía por soporte.
- El registro crea al destinatario como
pending_review: no aprueba y no libera retiros. La aprobación (KYC/AML) sigue haciéndola Coufai. Solo tras activarse, el destinatario aparece enGET /v1/recipientsy puede usarse enPOST /v1/withdrawvíarecipientId. - Idempotente por documento: reenviar el mismo CPF/CNPJ devuelve el mismo
recipientId(sin duplicar); un destinatario ya aprobado no se rebaja. - Name-match: la clave
cpf/cnpjdebe coincidir con eldocument. Límite por defecto de 10 destinatarios (activos + en análisis) por cuenta — ajustable por cuenta vía soporte.
2026-06-02 — Reintento automático de webhook
Las entregas de webhook que fallan por error transiente (timeout, error de red, HTTP 5xx, 429, 408) ahora se reentregan automáticamente con backoff: 1min → 5min → 30min → 2h → 6h (hasta 6 intentos, ~9h). Antes había un único intento automático — la recuperación dependía de polling o reenvío manual.
4xxno se reintenta (400/401/403/422…): significa que el endpoint rechazó el evento; martillar no ayudaría. Corrige la causa y usa el reenvío manual.eventIdse preserva entre intentos — deduplicar porX-Coufai-Event-Ides obligatorio. Eltimestampy laX-Coufai-Signaturese regeneran en cada intento; eleventIdno.- Con esto, se recomienda un flujo event-driven: reacciona al webhook + mantén tu saldo desde
netCents+ concilia ligero, sin polling agresivo deGET /v1/balance.
Cambio de comportamiento, no de contrato: ningún campo nuevo, ninguna acción necesaria para quien ya deduplica por eventId. Ver Reintento e idempotencia.
2026-05-27 — Normalización automática de document.type y pixKeyType + errores más informativos
Cambio aditivo: las solicitudes que ya envían valores correctos siguen funcionando sin ajustes. Lo que cambia es cómo manejamos variaciones y cómo reportamos errores.
Normalización automática (ley de Postel) en campos enum:
customer.document.typeenPOST /v1/pix: acepta"CNPJ","Cpf"," cnpj "etc. — las variaciones de case y whitespace se normalizan internamente a minúsculas. Antes, cualquier no-minúscula se convertía silenciosamente en"cpf"en la sanitización, haciendo que el proveedor rechazara números con tamaño incompatible y generando un502 Erro ao comunicar com gatewayconfuso.pixKeyTypeenPOST /v1/withdraw: misma normalización —"CPF","Email"," evp "aceptados.
Validación de tamaño del documento: customer.document.number debe tener 11 dígitos cuando type=cpf o 14 dígitos cuando type=cnpj (tras quitar puntuación). Antes, números con tamaño incompatible eran aceptados por Coufai y rechazados por el PSP.
Errores más informativos — todos los mensajes de 400 por validación de payload incluyen ahora el valor recibido (truncado) y lo esperado:
// Antes
{ "error": "Erro ao comunicar com gateway. Tente novamente." }
// status: 502
// Ahora
{ "error": "customer.document.type deve ser 'cpf' ou 'cnpj'. Recebido: 'CNPJ '." }
// status: 400Recomendación: enviar valores en minúsculas sin whitespace es el estándar limpio, pero la API ya no lo exige.
Ver Crear cobro, Iniciar retiro y Errores.
2026-05-09 — Retiro a terceros pre-aprobados vía API
POST /v1/withdraw ganó soporte al campo opcional recipientId para retirar a destinatarios terceros (CPF/CNPJ que no es el titular de la cuenta).
- Sin
recipientId(por defecto): comportamiento anterior — solo CPF/CNPJ del titular registrado, otros tipos rechazados. - Con
recipientId: libera los 5 tipos de clave PIX (cpf,cnpj,email,phone,evp) para un destinatario pre-aprobado por el equipo Coufai (compliance/AML).
La aprobación del recipientId es manual — contacta a soporte con el nombre legal y CPF/CNPJ del destinatario. No hay endpoint de auto-registro.
Nuevo endpoint GET /v1/recipients devuelve la lista de destinatarios activos pre-aprobados para tu cuenta con los respectivos recipientId — úsalos en POST /v1/withdraw. Ver Listar destinatarios.
Defensas actuales inalteradas: API key, IP allowlist, rate limit, anti-burst, caps diario/mensual, validación de saldo, idempotencia vía externalRef. Cada retiro a tercero dispara alerta interna en Coufai (auditoría continua).
Ver Iniciar retiro para schema completo, restricciones por tipo de clave y ejemplos.
2026-05-09 — Datos del PIX en webhooks y consulta REST
Nuevos campos opcionales entregados cuando la transacción fue efectivamente liquidada en la red SPI. Cambio aditivo — los clientes que no los usan los ignoran, sin adaptación.
-
Webhooks
transaction.paid,transaction.refunded,transaction.partially_refundedahora traen:endToEndId— E2E ID estándar BACEN del PIX (formatoE<ISPB><YYYYMMDDhhmm><id>).payer— quién pagó:{ name, document, documentType, bankAccount: { ispb, branch, account } }.
-
Webhooks
transfer.paid,transfer.refunded,transfer.partially_refundedahora traen:endToEndId— E2E ID del PIX OUT.beneficiary— quién recibió el retiro: misma estructura depayer.refundEndToEndId— solo en devoluciones (transfer.refunded/transfer.partially_refunded): E2E ID del PIX de devolución, distinto del PIX OUT original.
-
REST
GET /v1/transactions/:idyGET /v1/transactionsdevuelven los mismos campos cuando aplica. El polling pasa a ser fallback equivalente al webhook.
Eventos sin liquidación (transaction.failed, cancelled, expired, transfer.created, processing, pending_approval, approved, rejected, failed, cancelled) no traen esos campos — se omiten del JSON, no vienen como null.
Nota LGPD: payer.* y beneficiary.* contienen datos personales (nombre, CPF/CNPJ, cuenta bancaria). Tú eres el controlador del tratamiento posterior. Ver Eventos.
2026-05-08 — Multi-webhook + historial de entregas
- Múltiples URLs de webhook por cuenta (hasta 6). Cada URL filtra eventos por nombre canónico o por categoría semántica (
cashin,cashout,refund,dispute,chargeback). Ver Configurando webhook. - Catálogo de eventos canónicos estandarizado: 21 eventos
transaction.*ytransfer.*, con nombres estables enX-Coufai-Eventy en el campoeventdel cuerpo. Ver Eventos. - Eventos nuevos entregados por primera vez:
transaction.partially_refunded,transaction.infraction,transaction.dispute,transaction.protest,transaction.blocked,transaction.chargeback,transfer.created,transfer.processing,transfer.partially_refunded. - Header nuevo:
X-Coufai-Event-Idcon un identificador único por evento. Igual entre URLs del fanout y en reenvíos manuales — úsalo para dedup. - Historial de entregas en el panel (60 días de retención): timeline de cada intento con payload exacto, HTTP status y duración. Disponible en la transacción o en la lista global.
- Reenvío manual de webhook: botón para disparar un nuevo intento de una entrega histórica.
eventIdse preserva (idempotencia),timestampysignaturese regeneran. Límite 10/h por cuenta. Ver Reenvío manual.
Rupturas de naming (notar si integraste antes de mayo/2026)
Los siguientes nombres fueron reemplazados por los canónicos:
| Nombre antiguo | Nombre canónico actual |
|---|---|
TRANSFER_PAID | transfer.paid |
transaction.refunded.partial | transaction.partially_refunded |
transfer.refunded.partial | transfer.partially_refunded |
chargeback.applied | transaction.chargeback |
med.opened | transaction.infraction |
dispute.opened | transaction.dispute |
funds.blocked | transaction.blocked |
protest.opened | transaction.protest |
Si comparabas por el nombre antiguo, actualízalo. El status en el cuerpo (campo estable) sigue igual.
2026-05-08 — 2FA en el panel (sin impacto en la API)
- Los comerciantes ahora pueden activar autenticación en dos pasos (TOTP) en el panel, en Configuración → Seguridad.
- Sin cambios en el contrato de la API REST. Las API keys siguen funcionando exactamente como antes — sin código adicional, sin header nuevo, sin cambios en solicitudes o respuestas.
- Los retiros iniciados desde el panel pasan a exigir código TOTP en el momento de la operación. Los retiros vía
POST /v1/withdrawquedan inalterados (autenticación por API key + IP allowlist + rate limit).
2026-05 — Lanzamiento de la documentación pública
- Versión inicial cubriendo:
POST /v1/pix— creación de cobro PIX.POST /v1/withdraw— retiro PIX.GET /v1/balance— saldo.GET /v1/transactionsyGET /v1/transactions/:id— listado y consulta.- Webhook único entregado al endpoint configurado por el comerciante, firmado por HMAC.
[POR CONFIRMAR CON EL EQUIPO COUFAI]: alinear la política de versionado de la API (semver, breaking changes, plazos de deprecación) y el formato definitivo de este changelog.