PortuguêsChangelog

Changelog

Mudanças relevantes da API pública da Coufai.

2026-10-20 — Boleto: entrega só pela linha digitável

  • Em algumas contas o boleto é entregue só pela linha digitável, sem PDF: bankSlipUrl fica null de forma definitiva — mostre a identificationField ao pagador.
  • Depois que uma emissão em verificação é confirmada, o reenvio com a mesma externalRef devolve o boleto (200, "idempotent": true).

Veja Criar cobrança por boleto.

2026-10-15 — Boleto: dados completos do pagador e emissão em verificação

  • Novo objeto opcional customer.address (street, number, complement, district, city, zipCode, state) em POST /v1/boleto. Algumas contas passam a exigir e-mail, telefone e endereço do pagador: nelas, faltar ou errar um desses campos retorna 400 apontando qual, sem criar a cobrança, e o vencimento precisa ser hoje ou uma data futura. Nas demais contas o endereço é ignorado — recomendamos enviar sempre.
  • Logo após a emissão, bankSlipUrl e identificationField podem vir null enquanto o banco conclui o registro (normalmente menos de um minuto); consulte a transação em seguida.
  • Emissão em verificação: quando a emissão não confirma, a API responde 400 com "Boleto em verificação: a emissão não confirmou. NÃO reenvie — consulte a transação <id> em instantes.". Reenviar a mesma externalRef repete essa resposta até a confirmação, em vez de emitir um segundo boleto.
  • Falha passageira em que nada foi emitido passa a responder 503 com "Boleto temporariamente indisponível. Tente novamente em instantes." — pode reenviar. Erro inesperado continua 500 (a doc dizia 502 por engano).

Veja Criar cobrança por boleto.

2026-09-23 — Boleto: recusa da conta separada de recusa de dados

  • Quando a recusa é da conta (teto de emissão atingido ou emissão bloqueada), a API deixa de responder "Dados recusados pelo provedor: dados da cobrança inválidos. Corrija e reenvie. (invalid_action)" e passa a responder 400 com "Limite de emissão de cobranças da conta atingido. Não reenvie; fale com o suporte. (issuance_limit)" ou "Emissão não permitida para esta conta no momento. Não reenvie; fale com o suporte. (account_restricted)".
  • Nesses dois códigos, reenviar não resolve: não retente em loop, fale com o suporte. As recusas de dado (invalid_cpfCnpj, invalid_dueDate…) continuam iguais.

2026-09-12 — Taxa por boleto não pago

  • Contas com taxa de boleto não pago passam a ser cobradas quando um boleto vence sem pagamento. O transaction.expired de um boleto traz o novo campo opcional unpaidFeeCents (omitido quando não há taxa), e a cobrança aparece em GET /v1/transactions como uma linha type: "fee" (feeKind: "boleto_unpaid", relatedTransactionId = o boleto).
  • Boleto pago depois de vencido devolve a taxa automaticamente (linha type: "fee_reversal").
  • O saldo pode ficar negativo por causa da taxa; saques ficam recusados por saldo insuficiente até o próximo crédito cobrir.
  • GET /v1/transactions aceita ?type=fee e ?type=fee_reversal no filtro.

Veja Criar cobrança por boleto.

2026-09-12 — Boleto: recusa de dados vira 400 claro; telefone nunca bloqueia

  • Quando o provedor recusa um dado do pagador ou da cobrança, a API passa a responder 400 com "Dados recusados pelo provedor: <campo> inválido. Corrija e reenvie. (<código>)". Antes isso vinha como 500 genérico. <campo> diz o que corrigir e <código> é estável (ex.: invalid_cpfCnpj) — use o código na sua lógica, não o texto.
  • customer.phone é opcional: 11 dígitos = celular, 10 = fixo. Se o provedor recusar o telefone, o boleto é emitido sem ele; um telefone ruim não derruba mais a cobrança.

2026-09-06 — Boleto bancário via API

Novo endpoint POST /v1/boleto: emite um boleto e devolve o link público do PDF (bankSlipUrl, servido pela Coufai) e a linha digitável (identificationField). Vencimento opcional (dueDate, default hoje + 3 dias); mesma idempotência por externalRef do PIX.

  • O ciclo é o mesmo das cobranças PIX: pending → paid (evento transaction.paid, com netCents já descontada a taxa de boleto da conta) ou expired (transaction.expired) após o vencimento. O campo method vem como "boleto".
  • Habilitado por conta: sem habilitação, a chamada retorna 400 "Boleto não está habilitado para esta conta.". Fale com o suporte.
  • Pagamentos cujo aviso demorou são reconciliados com o banco em segundo plano e creditados mesmo assim — reaja ao webhook em vez de fazer polling agressivo.

Veja Criar cobrança por boleto.

2026-08-27 — Motivo da recusa em saques

Saques recusados agora informam por que falharam. Os eventos transfer.failed, transfer.rejected e transfer.cancelled — e o GET /v1/transactions/{id} — passam a trazer dois campos opcionais:

  • refusedCode — código estável em maiúsculas (ex.: INSUFFICIENT_BALANCE, DAILY_LIMIT_EXCEEDED, INVALID_PIX_KEY). É o campo para a sua lógica: não faça parsing do texto.
  • refusedReason — descrição legível, em português. Texto sujeito a mudança.

Antes, um saque recusado virava failed sem nenhuma indicação de causa, e a única saída era repetir a tentativa às cegas. Com o código, dá para distinguir o que adianta retentar (INSUFFICIENT_BALANCE, em minutos) do que não adianta sem correção (INVALID_PIX_KEY) ou só no dia seguinte (DAILY_LIMIT_EXCEEDED).

Compatível com o que já existe: os campos são omitidos quando o motivo não está disponível, e nenhum campo mudou de nome ou de tipo. Trate a ausência como “motivo não informado” — nunca como sucesso — e código desconhecido como recusa genérica, porque a lista pode crescer.

Saque recusado não debita: se o valor chegou a sair do saldo, ele volta antes de o evento ser enviado.

Veja Motivo da recusa.

2026-06-04 — Cadastro de destinatário terceiro via API

Novo endpoint POST /v1/recipients para submeter destinatários terceiros (CPF/CNPJ diferente do titular) direto pela API — antes, o cadastro só era feito pelo suporte.

  • O cadastro registra o destinatário como pending_review: não aprova e não libera saque. A aprovação (KYC/AML) continua sendo feita pela Coufai. Só após ativado o destinatário aparece em GET /v1/recipients e pode ser usado em POST /v1/withdraw via recipientId.
  • Idempotente pelo documento: reenviar o mesmo CPF/CNPJ retorna o mesmo recipientId (não duplica); um destinatário já aprovado não é rebaixado.
  • Name-match: chave cpf/cnpj precisa bater com o document. Limite padrão de 10 destinatários (ativos + em análise) por conta — ajustável por conta via suporte.

Veja Cadastrar destinatário.

2026-06-02 — Retry automático de webhook

Entregas de webhook que falham por erro transiente (timeout, erro de rede, HTTP 5xx, 429, 408) agora são reentregues automaticamente com backoff: 1min → 5min → 30min → 2h → 6h (até 6 tentativas, ~9h). Antes, havia uma única tentativa automática — a recuperação dependia de polling ou reenvio manual.

  • 4xx não é retentado (400/401/403/422…): significa que o endpoint recusou o evento; martelar não resolveria. Corrija a causa e use o reenvio manual.
  • eventId é preservado entre tentativas — deduplicar por X-Coufai-Event-Id é obrigatório. O timestamp e a X-Coufai-Signature são regenerados a cada tentativa; o eventId não.
  • Com isso, recomenda-se um fluxo event-driven: reaja ao webhook + mantenha seu saldo a partir de netCents + reconcile leve, sem polling agressivo de GET /v1/balance.

Mudança de comportamento, não de contrato: nenhum campo novo, nenhuma ação necessária para quem já deduplica por eventId. Veja Retentativa e idempotência.

2026-05-27 — Normalização automática de document.type e pixKeyType + erros mais informativos

Mudança aditiva: requests que já enviam valores corretos continuam funcionando sem ajuste. O que muda é como tratamos variações e como reportamos erros.

Normalização automática (Postel’s law) em campos enum:

  • customer.document.type em POST /v1/pix: aceita "CNPJ", "Cpf", " cnpj " etc. — variações de case e whitespace são normalizadas internamente para lowercase. Antes, qualquer não-lowercase era silenciosamente convertido em "cpf" no sanitize, fazendo o provedor rejeitar números com tamanho incompatível e gerando 502 Erro ao comunicar com gateway confuso.
  • pixKeyType em POST /v1/withdraw: mesma normalização — "CPF", "Email", " evp " aceitos.

Validação de tamanho de documento: customer.document.number precisa ter 11 dígitos quando type=cpf ou 14 dígitos quando type=cnpj (após remover pontuação). Antes, números com tamanho incompatível eram aceitos pela Coufai e rejeitados pelo PSP.

Erros mais informativos — todas as mensagens de 400 em validação de payload agora incluem o valor recebido (truncado) e o esperado:

// Antes
{ "error": "Erro ao comunicar com gateway. Tente novamente." }
// status: 502
 
// Agora
{ "error": "customer.document.type deve ser 'cpf' ou 'cnpj'. Recebido: 'CNPJ '." }
// status: 400

Recomendação: enviar valores em lowercase sem whitespace é o padrão limpo, mas a API não exige mais.

Veja Criar cobrança, Iniciar saque e Erros.

2026-05-09 — Saque a terceiros pré-aprovados via API

POST /v1/withdraw ganhou suporte ao campo opcional recipientId para sacar para destinatários terceiros (CPF/CNPJ que não é o titular da conta).

  • Sem recipientId (default): comportamento de antes — só CPF/CNPJ do titular cadastrado, outros tipos rejeitados.
  • Com recipientId: libera os 5 tipos de chave PIX (cpf, cnpj, email, phone, evp) para um destinatário pré-aprovado pelo time Coufai (compliance/AML).

A aprovação do recipientId é manual — contate o suporte informando o nome legal e CPF/CNPJ do destinatário. Não há endpoint para auto-cadastro.

Novo endpoint GET /v1/recipients retorna a lista de destinatários ativos pré-aprovados para sua conta com os respectivos recipientId — use-os no POST /v1/withdraw. Veja Listar destinatários.

Defesas atuais inalteradas: API key, IP allowlist, rate limit, anti-burst, caps diário/mensal, validação de saldo, idempotência via externalRef. Cada saque a terceiro dispara alerta interno na Coufai (auditoria contínua).

Veja Iniciar saque para schema completo, restrições por tipo de chave e exemplos.

2026-05-09 — Dados do PIX nos webhooks e na consulta REST

Novos campos opcionais entregues quando a transação foi efetivamente liquidada na rede SPI. Mudança aditiva — clientes que não usam ignoram, sem necessidade de adaptação.

  • Webhooks transaction.paid, transaction.refunded, transaction.partially_refunded agora trazem:

    • endToEndId — E2E ID padrão BACEN do PIX (formato E<ISPB><YYYYMMDDhhmm><id>).
    • payer — quem pagou: { name, document, documentType, bankAccount: { ispb, branch, account } }.
  • Webhooks transfer.paid, transfer.refunded, transfer.partially_refunded agora trazem:

    • endToEndId — E2E ID do PIX OUT.
    • beneficiary — quem recebeu o saque: mesma estrutura de payer.
    • refundEndToEndId — apenas em devoluções (transfer.refunded / transfer.partially_refunded): E2E ID do PIX de devolução, distinto do PIX OUT original.
  • REST GET /v1/transactions/:id e GET /v1/transactions retornam os mesmos campos quando aplicáveis. Polling vira fallback equivalente ao webhook.

Eventos sem liquidação (transaction.failed, cancelled, expired, transfer.created, processing, pending_approval, approved, rejected, failed, cancelled) não trazem esses campos — omitidos do JSON, não vêm como null.

Atenção LGPD: payer.* e beneficiary.* contêm dados pessoais (nome, CPF/CNPJ, conta bancária). Você é o controlador do tratamento posterior. Ver Eventos.

2026-05-08 — Multi-webhook + histórico de entregas

  • Múltiplas URLs de webhook por conta (até 6). Cada URL filtra eventos por nome canônico ou por categoria semântica (cashin, cashout, refund, dispute, chargeback). Veja Configurando webhook.
  • Catálogo de eventos canônicos padronizado: 21 eventos transaction.* e transfer.*, com nomes estáveis no X-Coufai-Event e no campo event do corpo. Veja Eventos.
  • Eventos novos entregues pela primeira vez: transaction.partially_refunded, transaction.infraction, transaction.dispute, transaction.protest, transaction.blocked, transaction.chargeback, transfer.created, transfer.processing, transfer.partially_refunded.
  • Header novo: X-Coufai-Event-Id com um identificador único por evento. Mesmo entre URLs do fanout e em reenvios manuais — use para dedupe.
  • Histórico de entregas no painel (60 dias de retenção): timeline de cada tentativa com payload exato, status HTTP e duração. Disponível na transação ou na lista global.
  • Reenvio manual de webhook: botão pra disparar nova tentativa de uma entrega histórica. eventId é preservado (idempotência), timestamp e signature regenerados. Limite 10/h por conta. Veja Redespacho manual.

Quebras de naming (notar se você integrou antes de maio/2026)

Os nomes a seguir foram substituídos pelos canônicos:

Nome antigoNome canônico atual
TRANSFER_PAIDtransfer.paid
transaction.refunded.partialtransaction.partially_refunded
transfer.refunded.partialtransfer.partially_refunded
chargeback.appliedtransaction.chargeback
med.openedtransaction.infraction
dispute.openedtransaction.dispute
funds.blockedtransaction.blocked
protest.openedtransaction.protest

Se você comparava pelo nome antigo, atualize. O status no corpo (campo estável) continua igual.

2026-05-08 — 2FA no painel (sem impacto na API)

  • Lojistas agora podem ativar autenticação em dois fatores (TOTP) no painel, em Configurações → Segurança.
  • Nenhuma mudança no contrato da API REST. API keys continuam funcionando exatamente como antes — sem código adicional, sem header novo, sem mudança em requests ou respostas.
  • Saques iniciados pelo painel passam a exigir código TOTP no momento da operação. Saques via POST /v1/withdraw permanecem inalterados (autenticação por API key + IP allowlist + rate limit).

2026-05 — Lançamento da documentação pública

  • Versão inicial cobrindo:
    • POST /v1/pix — criação de cobrança PIX.
    • POST /v1/withdraw — saque PIX.
    • GET /v1/balance — saldo.
    • GET /v1/transactions e GET /v1/transactions/:id — listagem e consulta.
    • Webhook único entregue ao endpoint configurado pelo lojista, assinado por HMAC.

[VERIFICAR COM EQUIPE COUFAI]: alinhar política de versionamento da API (semver, breaking changes, prazos de deprecação) e formato definitivo deste changelog.