A conciliação manual de pagamentos matou a escalabilidade de mais startups brasileiras do que a falta de capital. No modelo de assinatura B2B, onde faturas de R$ 12.000,00 precisam ser reconhecidas em segundos para liberar acesso à plataforma, contar com um analista exportando CSVs do banco não é opção. O PIX Cobrança resolve a liquidação financeira (T+0), mas a dor de cabeça real move-se para o backend: garantir que o estado do seu sistema reflita a realidade do banco central sem janelas de inconsistência.
O erro clássico é confiar que o usuário clicou no botão "Pagar" no app do banco. Ele pode ter gerado a chave pix, clicado fora da tela e deixado a transação pendente por horas. Seu sistema só pode liberar o serviço quando o webhook chegar, e ele precisa ser processado com uma lógica de estado à prova de falhas. A documentação oficial do Banco Central e dos PSPs (Instituições de Pagamento) sobre o fluxo de pending para confirmed é clara, mas a implementação correta exige rigidez no tratamento de concorrência.
Aqui está o processo técnico para blindar sua receita.
A armadilha de confiar apenas na resposta síncrona da criação
Quando seu backend chama o endpoint POST /v2/cob da API do seu PSP, a resposta retorna o loc (local do QR Code) e o status criado ou ativo. Muitos desenvolvedores, por ansiedade, atualizam o status da fatura no banco de dados para "aguardando pagamento" e voltam a tela para o usuário, exibindo o QR Code. O problema surge quando o cliente faz o pagamento 30 segundos depois, mas o webhook só chega 2 minutos depois devido a uma latência na rede do PSP.

Se você consultou o status via API polling (chamadas periódicas GET) para acelerar o processo, criou uma race condition: a requisição de consulta chega 500ms antes do webhook. Ambos veem o pagamento como concluido. Ambos tentam atualizar o saldo do cliente e enviar o e-mail de boas-vindas. O resultado? E-mail duplicado e, se não houver trava no banco de dados, créditos em dobro no sistema. Em 2026, com APIs de Banking-as-a-Service (BaaS) integradas diretamente ao SaaS, esse tipo de erro é inaceitável, pois mexe com tesouraria real.
Portanto, o passo zero é aceitar que a API de criação é síncrona, mas a confirmação é um evento assíncrono. Seu banco de dados deve tratar o pagamento como pending_payment imediatamente após a criação da cobrança, e só migrar para paid sob estrita condicionalidade do ID da transação (e2eId).
Validação de segurança: assinatura HMAC é obrigatória
Nunca processe um webhook apenas porque ele chegou na sua rota /api/webhooks/pix. Qualquer pessoa na internet pode disparar um POST para esse endpoint com o payload { "status": "CONCLUIDA" }. Se seu código der crédito nessa condição, você sofrerá um ataque massivo de fraudes que vai drenar seu caixa em minutos.
Todo PSP respeitável envia um cabeçalho HTTP, geralmente chamado x-signature ou digital-signature, contendo um hash HMAC SHA-256 do payload usando uma chave secreta que só você e o PSP conhecem.
A lógica de validação deve ser o middleware (intermediário) da sua rota:
- Pegue o "body" bruto da requisição (não o objeto JSON parseado) para garantir que a formatação de espaço ou quebra de linha não altere o hash.
- Concatene o timestamp (se enviado) com o payload bruto.
- Compute o HMAC SHA-256 usando a
WEBHOOK_SECRET_KEY que você configurou no dashboard do PSP.
- Compare o hash gerado com o header recebido. Use uma função de comparação constante-time (como
crypto.timingSafeEqual em Node.js) para evitar ataques de timing.
- Se não bater, retorne HTTP 401 imediatamente. Se bater, processe.
Ignorar esse passo é uma violação grave de segurança regulatória. O PIX caiu no gosto popular justamente pela liquidez, e isso atrai golpistas.
Estrutura do banco de dados para transições de estado atômicas
Para implementar a reconciliação automática, sua tabela de transações (financial_transactions) não pode ter colunas de status soltas. Você precisa de uma coluna status (enum: created, pending, confirmed, failed, canceled) e, crucialmente, uma coluna pix_e2e_id que aceite nulos e tenha um índice único.
O e2eId (End-to-End ID) é o identificador universal da transação PIX no Banco Central. Ele é a única garantia de idempotência. Quando o webhook chega, ele trás esse campo.
Exemplo de fluxo SQL seguro dentro de uma transação:
BEGIN TRANSACTION;
-- 1. Tenta bloquear a linha para evitar leitura suja (SELECT FOR UPDATE)
SELECT * FROM financial_transactions
WHERE pix_e2e_id = 'E1823612021625214132101103123412'
FOR UPDATE;
-- 2. Se não existe, insere (caso seu sistema crie a transação na resposta do webhook, o que não recomendo para PIX Cobrança, mas é comum em Key PIX estático)
-- OU, no caso de PIX Cobrança onde a transação já existe com status 'pending':
UPDATE financial_transactions
SET status = 'confirmed',
confirmed_at = NOW(),
pix_e2e_id = 'E1823612021625214132101103123412'
WHERE id = 4589 AND status IN ('pending', 'created');
-- 3. Verifica se o update aconteceu. Se o rowCount for 0, significa que essa transação já estava confirmada ou não existe.
COMMIT;
Essa estrutura resolve o problema de webhooks duplicados. Os PSPs enviam o mesmo evento múltiplas vezes se não recebem um HTTP 200 OK. Se você tentar atualizar uma linha que já está confirmed com a lógica acima (ou se o e2eId já estiver preenchido e confirmado em outra linha), o update simplesmente não afeta linhas, e seu código pode ignorar o processamento secundário (envio de email, liberação de crédito), mantendo a integridade dos dados.

A imagem acima ilustra o ambiente real onde isso acontece: monitorando logs para garantir que o e2eId está sendo salvo corretamente. Perder esse ID na criação da cobrança é um erro comum que torna a reconciliação impossível.
Processando o evento 'CONCLUIDA' e o perigo da race condition
Chegamos no núcleo da lógica B2B. Ao receber o payload com status CONCLUIDA, seu backend deve:
- Validar o valor recebido no webhook contra o valor da fatura na sua base. Ataques de man-in-the-middle ou erros de integração raramente ocorrem, mas a verificação de valor é barata e salva muito dinheiro. Um webhook dizendo que você recebeu R$ 50,00 para uma fatura de R$ 5.000,00 deve ser descartado/logado como erro.
- Iniciar a transação atômica no banco (conforme descrito acima).
- Atualizar o status.
- Enfileirar eventos de consequência (publicar no RabbitMQ/SQS/SNS). Não envie o e-mail de "Obrigado pelo pagamento" dentro do controller do webhook. Isso atrasa a resposta ao PSP. Se o seu sistema de e-mail demorar 2 segundos, o PSP vai achar que seu endpoint caiu e reenviar o webhook, gerando duplicidade na fila de mensagens. Retorne HTTP 200 o mais rápido possível e delegue o trabalho pesado para workers assíncronos.
O tratamento de status pending geralmente ocorre quando o QR Code foi gerado, mas o pagamento ainda não passou pela centralização do BCB. Algumas APIs enviam ATIVA, outras PENDING. Para sua lógica interna, trate ambos como "Aguardando Liquidação". O PIX é finalidade instantânea, mas existe um janelinha técnica. Não considere o dinheiro como seu até o CONCLUIDA (ou REMOVIDA_PELO_USUARIO_RECEBEDOR em casos de devolução, embora raro em cobrança imediata).
Estratégia de fallback com consulta manual (Polling)
Webhooks podem falhar. Seu servidor cai, seu certificado SSL expira ou o PSP tem problemas de roteamento. Se você deixar apenas o webhook cuidar da conciliação, sua fatura ficará em aberto eternamente até que um cliente reclame.
Você precisa de um cron job ou scheduler que rode a cada 5 ou 10 minutos. A tarefa dele é simples mas vital:
- Buscar transações com status
pending criadas a mais de 15 minutos.
- Para cada uma, fazer uma chamada
GET /v2/cob/{txid} na API do PSP.
- Se a resposta vier como
CONCLUIDA ou PAGO, dispara a mesma lógica de atualização do banco que o webhook usaria.
Isso cria uma rede de segurança. Se o webhook falhar, o polling "corrige" o estado em, no máximo, 15 minutos. Uma ressalva técnica: tome cuidado com rate limiting. Se você tiver 10.000 pagamentos pendentes num dia de Black Friday, seu cron job pode ser bloqueado pela API do PSP. Ajuste a janela de busca para, no máximo, as últimas 2 horas, e filtre apenas os txid mais críticos.
Lidando com estornos e devoluções no fluxo B2B
O PIX permite devolução (estorno) pelo pagador ou recebedor em até 90 dias. No contexto B2B, seu cliente (a empresa pagadora) pode pedir o chargeback se o serviço não foi entregue, ou o banco pode identificar uma fraude.
Os webhooks de devolução chegam com o status DEVOLVIDA. Sua lógica aqui deve ser ainda mais rígida. Ao receber esse evento:
- Bloqueie o acesso do cliente imediatamente (se o valor devolvido zerar o saldo).
- Registre o motivo da devolução (ID da devolução na API).
- Notifique financeiramente seu time ou, se for SaaS automatizado, estorne os créditos da conta SaaS.
A maioria dos desenvolvedores foca apenas no "pagamento aceito" e esquece o "pagamento cancelado". Em 2026, ferramentas de conciliação financeira inteligentes já são padrão em ERPs como Totvs e RM, mas se o seu SaaS não fizer a escrituração reversa automaticamente, seu balanço não fechará.
Construir um sistema de pagamentos robusto no Brasil exige respeitar as idiossincrasias do PIX. Ele é rápido, mas a assincronicidade das notificações é o ponto de estrangulamento. Se seu backend tratar o webhook como uma "dica" de status e não como a "verdade absoluta", você evita o erro clássico de liberar serviço para um cliente que vai ter o pagamento estornado no dia seguinte. Implemente travas de banco, valide a assinatura do payload e mantenha o job de polling ativo. A automação só traz tranquilidade quando o sistema sabe lidar com o caos.