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.