Receber webhook de pagamento Pix
Resposta rápida: Publique a rota base e a rota com sufixo
/pix, registre a URL comPUT /webhook/{chave}protegida por secret, aceite os três formatos de payload, confirme comGET /pix/{endToEndId}antes de creditar e processe com idempotência porendToEndId.
Pré-requisitos
- Consultar cobrança como fallback
- URL pública HTTPS (
{{WEBHOOK_URL}}) - Referência webhooks
Passo a passo
- Implemente duas rotas: a base (
{{WEBHOOK_URL}}) e a mesma com/pixno 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. - Exponha com TLS válido.
- Registre com
PUT /webhook/{chave}, pondo o secret na query string, e leia de volta comGETpara confirmar que a URL sobreviveu intacta. - Autentique por secret (query ou header). Não existe assinatura nesta API — ver referência de webhooks.
- 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. - Confirme antes de creditar:
GET /pix/{endToEndId}autenticado. O webhook é dica, não verdade. - Idempotência por
endToEndId— o PSP entrega o mesmo evento mais de uma vez por pagamento. - 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.
- 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.mjssnippets/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
| Sintoma | Causa | Ação |
|---|---|---|
| PSP não entrega, registro devolve 200 | Sufixo /pix sem rota | Publicar a rota com sufixo; testar com curl externo |
| PSP parou de entregar depois de uma falha | Canal 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 200 | Payload em formato não previsto, descartado calado | Aceitar os três formatos; logar o shape do body |
| Duplicatas | Sem idempotência | Chave por endToEndId |
| Pedido travado com pagamento feito | Webhook mudo e fallback só olhando cobrança expirada | Varredura de pendentes por GET /cob/{txid}, não só das vencidas |
| Callback forjado mudaria estado | Confiança no body | Confirmar com GET /pix/{endToEndId} antes de creditar |
Checklist
- HTTPS válido
- Rota base e rota com sufixo
/pixpublicadas - 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.