Blog

Arquitetura de integração com Pix: o que muda no seu backend.

Integrar Pix parece simples do lado da API: gera cobrança, recebe notificação, marca como pago.

O que muda de verdade não está na API. Está em três premissas que o seu sistema provavelmente carrega desde que foi escrito, e que deixam de valer.

Premissa 1: existe janela de processamento

Cartão e boleto têm ritmo. Boleto compensa em dia útil. Cartão tem autorização e captura separadas, com horas ou dias no meio. Isso permite lote noturno, conciliação diária e a ideia confortável de que existe um momento em que nada está acontecendo.

Pix acontece a qualquer hora. Três da manhã de domingo, feriado, durante o seu deploy.

Consequências práticas:

Não existe janela de manutenção silenciosa. Se o seu sistema fica cinco minutos fora durante o deploy, ele fica cinco minutos sem receber notificação. As notificações voltam depois, com retentativa, e o seu consumidor precisa aguentar a rajada.

Fechamento de dia deixa de ser natural. O corte é uma convenção sua, e transações na fronteira vão cair de um lado ou do outro dependendo do fuso e do critério. Isso precisa estar decidido e documentado, senão a conciliação acusa divergência todo dia.

Job diário vira job contínuo. O que rodava às 2h precisa rodar sempre.

Premissa 2: dá pra estornar depois

Cartão tem chargeback: o cliente contesta e o dinheiro volta semanas depois. Isso permite ser otimista, liberar o produto e resolver depois se der problema.

Pix é liquidado na hora e não tem contestação nesse sentido. A devolução é uma operação nova, que você inicia voluntariamente, com regras e prazo próprios.

O que isso muda no desenho: você não pode liberar valor antes de confirmar o recebimento pela sua fonte de verdade. E “confirmar” não é receber um webhook: é ter consultado o status na origem ou ter validado a autenticidade da notificação de forma robusta.

Esse ponto vira crítico porque golpe de comprovante falso é comum. Uma imagem de comprovante não é confirmação de nada, e sistema que libera com base em comprovante enviado pelo cliente vai perder dinheiro. A confirmação vem do provedor, sempre.

Premissa 3: cada cobrança tem um pagador esperado

Com boleto, a cobrança carrega o valor e o pagador. Com Pix, existem dois modos e eles têm implicações diferentes.

Cobrança dinâmica, com valor e identificador atrelados, se parece com boleto e é o caminho mais fácil de conciliar.

Chave estática, ou QR estático, aceita qualquer valor de qualquer pessoa a qualquer hora. Ótimo pra receber, péssimo pra conciliar: chega dinheiro sem nenhuma referência ao que ele paga.

Se o produto usa chave estática, você vai ter a categoria “recebimento sem identificação”, e ela precisa de tratamento: fila de conciliação manual, regra de casamento por valor e janela, ou um processo de o cliente informar o pagamento. Ignorar essa categoria é como se acumula dinheiro na conta que ninguém sabe a quem pertence.

O desenho que eu usaria

create table pix_charge (
  id              uuid primary key,
  txid            text unique not null,     -- o identificador da cobrança
  amount_minor    bigint not null,
  status          text not null,            -- 'active','paid','expired','cancelled'
  expires_at      timestamptz not null,
  order_id        uuid references orders(id),
  created_at      timestamptz not null default now()
);

create table pix_movement (
  id              uuid primary key,
  provider_id     text not null,            -- id do recebimento no provedor
  txid            text,                     -- pode ser nulo em QR estático
  amount_minor    bigint not null,
  payer_document  text,
  occurred_at     timestamptz not null,
  recorded_at     timestamptz not null default now(),
  raw             jsonb not null,           -- o payload como veio
  unique (provider_id)
);

Duas tabelas separadas, e essa separação é a decisão principal. pix_charge é a sua intenção de receber. pix_movement é o dinheiro que entrou. Elas se relacionam quando dá, e ficam soltas quando não dá.

Sistema que tem só uma tabela não consegue representar “entrou dinheiro que não corresponde a nenhuma cobrança”, que é uma situação real e frequente.

A constraint única em provider_id é o que impede notificação reenviada de virar dinheiro contado duas vezes. Como escrevi em idempotência em pagamentos, isso precisa ser resolvido pelo banco, não por verificação na aplicação.

E guarde o raw. O payload cru como recebido é a única coisa que permite reconstruir o que aconteceu quando algo der errado seis meses depois.

Webhook, e por que ele não basta

O provedor notifica. Notificação se perde: rede, deploy, timeout, erro seu que devolveu 500.

Todo sistema de Pix precisa de dois caminhos:

O webhook, que dá a latência baixa que o produto quer.

E uma varredura periódica que consulta os recebimentos do período e casa com o que você tem. Essa é a rede de segurança, e é ela que pega o webhook perdido.

Sem o segundo caminho, existe uma categoria de cliente que pagou e não foi atendido, e você descobre pelo suporte.

O que não subestimar

Devolução tem regra e prazo, e o fluxo precisa existir no produto desde o começo. Cliente que paga o valor errado é comum, e resolver isso na mão via painel do banco não escala.

O relógio importa. Com liquidação instantânea, a diferença entre o horário do provedor e o seu vira divergência de conciliação. Use o horário da origem como occurred_at e o seu como recorded_at, sempre separados.

E a chave Pix da empresa é dado sensível de negócio. Ela aparece em QR, em documento, em e-mail. Trocar depois é caro, e vazamento associado a golpe de troca de chave é um risco real que vale considerar no processo de emissão de cobrança.

← Voltar pro blog