Pular para o conteúdo principal

Receber webhook de pagamento Pix

Resposta rápida: Publique a rota base e a rota com sufixo /pix, registre a URL com PUT /webhook/{chave} protegida por secret, aceite os três formatos de payload, confirme com GET /pix/{endToEndId} antes de creditar e processe com idempotência por endToEndId.

Pré-requisitos​

Passo a passo​

  1. Implemente duas rotas: a base ({{WEBHOOK_URL}}) e a mesma com /pix no fim. O BACEN acrescenta o sufixo sozinho — a base atende o probe do registro, a com sufixo recebe as entregas. Sem as duas, todo callback vira 404.
  2. Exponha com TLS válido.
  3. Registre com PUT /webhook/{chave}, pondo o secret na query string, e leia de volta com GET para confirmar que a URL sobreviveu intacta.
  4. Autentique por secret (query ou header). Não existe assinatura nesta API — ver referência de webhooks.
  5. Aceite os três formatos de payload: {"pix":[...]}, o objeto pix plano na raiz e o envelope {"type","data"}. Quando nenhum casar, logue as chaves de primeiro nível do body em vez de descartar calado.
  6. Confirme antes de creditar: GET /pix/{endToEndId} autenticado. O webhook é dica, não verdade.
  7. Idempotência por endToEndId — o PSP entrega o mesmo evento mais de uma vez por pagamento.
  8. Responda 200 depois de aceitar o evento (ou de enfileirá-lo). Nunca responda 200 para um payload que você não entendeu: isso cancela o retry.
  9. Mantenha o fallback ativo — uma varredura periódica de cobranças pendentes com GET /cob/{txid}, porque webhook mudo acontece.

Código​

Fontes:

  • snippets/node/webhook-receiver.mjs
  • snippets/python/webhook_receiver.py

Node.js (stub local)​

// source: snippets/node/webhook-receiver.mjs
// run: node snippets/node/webhook-receiver.mjs

Python (stub local)​

# source: snippets/python/webhook_receiver.py

Se der errado​

SintomaCausaAção
PSP não entrega, registro devolve 200Sufixo /pix sem rotaPublicar a rota com sufixo; testar com curl externo
PSP parou de entregar depois de uma falhaCanal do painel pausado ("pausar envio ao detectar falha" vem marcado)Reativar o canal no painel antes de investigar rede
Chega o POST, nada acontece, resposta 200Payload em formato não previsto, descartado caladoAceitar os três formatos; logar o shape do body
DuplicatasSem idempotênciaChave por endToEndId
Pedido travado com pagamento feitoWebhook mudo e fallback só olhando cobrança expiradaVarredura de pendentes por GET /cob/{txid}, não só das vencidas
Callback forjado mudaria estadoConfiança no bodyConfirmar com GET /pix/{endToEndId} antes de creditar

Checklist​

  • HTTPS válido
  • Rota base e rota com sufixo /pix publicadas
  • Secret conferido (errado → 401, certo → 200)
  • Os três formatos de payload cobertos por teste
  • Confirmação via GET /pix/{endToEndId} antes de creditar
  • Idempotência testada com entrega repetida
  • Fallback de varredura de pendentes

Próximo passo​

Happy path v1 completo. Expansões (cobv, lotes) ficam para o próximo ciclo do pack.

FAQ​

P: Webhook substitui GET cob?​

R: Não. Aqui ele é só um aviso: quem decide é o GET autenticado.

P: Como sei que o processo em produção realmente tem o secret configurado?​

R: Faça o flip: POST com secret errado deve dar 401, e com o secret certo, 200. Ler o painel de variáveis do PaaS não prova nada sobre o processo que está rodando.