Quando o webhook Pix não chega, a causa quase sempre está em um de seis pontos: o endpoint não está ativo ou não assina o evento, a URL não é alcançável de fora, um firewall barra o POST, o handler não responde 2xx, a assinatura é recusada porque o corpo foi reprocessado, ou o evento esperado simplesmente não existe. O roteiro abaixo verifica cada um, na ordem do mais barato para o mais trabalhoso. O funcionamento do webhook da xFlow, com eventos e código do handler, está no guia de webhook Pix.
Primeiro: não chega ou chega e é recusado?
São dois problemas diferentes, e o log decide qual é o seu. Registre toda requisição que bate na rota do webhook logo na entrada, antes de qualquer validação: horário, método, caminho, o header x-webhook-event e o tamanho do corpo.
- Nada aparece no log. A entrega não está alcançando o seu código. Siga os passos 1 a 3.
- A requisição aparece, mas a resposta é
401,403,404ou5xx. A entrega chega e o seu lado recusa. Para a xFlow, isso é falha, e o evento entra em reenvio até desistir. Siga os passos 4 e 5.
1. O endpoint está ativo e assina o evento
Liste os endpoints da conta:
curl https://app.xflowpayments.com/api/v1/webhooks \
-u "pk_live_...:sk_live_..."Para cada item, confira:
url: o endereço exato, com o caminho certo. Uma barra a mais ou a menos no fim já pode levar a uma rota diferente no seu servidor.events: o endpoint só recebe os eventos da lista. Setransaction.paidnão está lá, a confirmação de pagamento não chega.status: um endpoint pausado comPATCH /api/v1/webhooks/{id}e{ "active": false }não recebe nada até ser reativado.
Confira também se o evento que você espera existe. Não há evento de cobrança expirada. Os eventos dispute.* só chegam quando a conta tem o recurso de disputas ativo. E com chave de teste não há saque, então não há eventos withdrawal.* no sandbox.
2. A URL é HTTPS, pública e responde de fora
No cadastro, a URL precisa ser https:// e pública, e a xFlow recusa com 400 invalid_url um endereço inacessível ou de rede interna. Mas uma URL aceita no cadastro pode deixar de funcionar depois: o DNS mudou, o certificado venceu, o túnel de teste fechou ou o servidor trocou de endereço.
Teste de uma máquina fora da sua rede, como um celular sem Wi-Fi da empresa ou um servidor em outra nuvem:
curl -i -X POST https://seusistema.com.br/webhooks/xflow \
-H "Content-Type: application/json" \
-d '{"teste":true}'A resposta esperada é o 401 do seu próprio handler, porque o corpo não tem assinatura válida. Esse 401 prova que a rota é alcançável pela internet. Se vier erro de certificado, tempo esgotado, 404 ou uma página HTML de outro sistema, o problema está no caminho até o handler.
Dois cuidados nessa etapa:
- Certificado válido. Use um certificado emitido por autoridade reconhecida e com a cadeia completa. A documentação não detalha a validação feita na entrega, então não conte com certificado autoassinado.
- Sem redirecionamento. A documentação não diz se a entrega segue redirecionamentos. Cadastre a URL final, que responde direto, sem passar por
301ou302.
3. Firewall, WAF e proxy
Um POST sem cookie, sem navegador e vindo de fora é exatamente o que muitas proteções bloqueiam. Procure nos registros do firewall, do WAF ou do CDN, no horário do pagamento, por requisições barradas na rota do webhook. Causas comuns:
- regra de proteção contra robôs que devolve uma página de desafio;
- limite de requisições por origem que corta rajadas de eventos;
- limite de tamanho de corpo muito baixo;
- lista de IPs permitidos no seu servidor.
A documentação da xFlow não publica os IPs de origem das entregas. Não filtre o webhook por IP: libere a rota e use a assinatura como controle. A allowlist de IP do painel da xFlow é outra coisa: ela limita de onde saem as chamadas que você faz à API.
4. O handler responde 2xx, e rápido
A xFlow considera a entrega confirmada quando recebe 2xx e reenvia até 3 vezes quando não recebe. A documentação não informa o tempo limite de cada tentativa nem o intervalo entre elas. Por isso, o handler deve fazer o mínimo antes de responder: validar a assinatura, gravar o evento e devolver 200. E-mail, nota fiscal e liberação de acesso vão para uma fila.
Respostas fora de 2xx que costumam passar despercebidas:
302ou401de autenticação. Um middleware de login protege todas as rotas, inclusive a do webhook.403ou419de CSRF. A proteção CSRF de frameworks como Laravel, Django e Rails pode recusar umPOSTsem token. Exclua a rota do webhook dessa proteção; a assinatura HMAC faz esse papel.404ou405. A rota existe só paraGET, ou o caminho cadastrado é diferente do publicado.500depois do trabalho feito. O pedido foi processado, mas uma exceção no fim derrubou a resposta. A xFlow reenvia, e um handler sem idempotência processa de novo.
5. A assinatura é recusada: o corpo foi reprocessado
O header x-webhook-signature traz sha256= e o HMAC-SHA256 do corpo cru, calculado com o segredo do endpoint. Se o seu framework transforma o corpo em objeto e você serializa de novo para validar, os bytes mudam e a assinatura não bate. O seu handler responde 401, a xFlow reenvia, e para quem olha de fora parece que o webhook "não chega".
Pontos para conferir:
- Leitura do corpo cru. No Express, um
express.json()global roda antes da rota; useexpress.rawsó na rota do webhook. No Next.js, leia comawait request.text(), e não comrequest.json(). No Laravel, use$request->getContent(). - Segredo certo. Cada endpoint tem o seu segredo, mostrado uma única vez na criação. Se você apagou e recriou o endpoint, o segredo mudou.
- Segredo limpo. Uma quebra de linha no fim da variável de ambiente, comum em arquivos
.enve em segredos criados comecho, muda o HMAC. - Comparação. Compare a string inteira, com o prefixo
sha256=, em tempo constante.
Para tirar a dúvida, grave o corpo cru e o header de uma entrega recusada e cole os dois, com o segredo, no validador de assinatura de webhook. Se lá confere e no seu código não, o corpo está sendo alterado antes do cálculo.
6. Reproduza no sandbox
Com a chave de teste, crie uma cobrança e chame POST /api/v1/test/charges/{id}/pay. A cobrança vira paid e o evento transaction.paid é disparado com livemode: false. Se esse evento chega à sua URL, a entrega funciona e a causa está na configuração de produção.
Atenção a um falso negativo: um handler correto descarta eventos com livemode: false antes de dar baixa em pedidos. No teste, registre o evento antes desse filtro, ou você vai concluir que ele não chegou. Para testar na sua máquina, com túnel HTTPS e reenvio manual de eventos, siga como testar webhook localmente.
Enquanto isso: a consulta como plano B
Webhook é o caminho principal, não o único. Enquanto investiga, não deixe pedidos parados: consulte GET /api/v1/charges/{id} para as cobranças em aberto e rode uma varredura periódica com GET /api/v1/charges?status=paid e o período, para recuperar pagamentos cujo aviso se perdeu. Quando e como usar cada caminho está em como saber se o Pix foi pago pela API.
Sintomas e onde olhar
| Sintoma | Onde olhar |
|---|---|
| Nenhum evento, de nenhum tipo | GET /api/v1/webhooks: URL, status e events; teste de fora com curl |
Chegam pending, mas não paid |
Lista events do endpoint |
| Parou de chegar de um dia para o outro | Certificado, DNS, mudança de servidor, regra nova no WAF |
| Chega em duplicidade | O handler não respondeu 2xx a tempo; torne-o idempotente |
Log mostra 401 do seu handler |
Corpo reprocessado ou segredo errado |
| Só não chega no teste | Filtro de livemode antes do log |
Quando falar com a xFlow
Antes de abrir chamado, veja a página de status, que mostra a situação dos webhooks. Se o problema continuar, envie o id do endpoint, o id da cobrança, o evento esperado e o horário do pagamento pelos canais da página de contato. A referência completa dos webhooks está na documentação da API.
Perguntas frequentes
Quantas vezes a xFlow tenta entregar um webhook?
A documentação diz que a entrega é reenviada até 3 vezes e que a sua URL confirma o recebimento respondendo 2xx. O intervalo entre as tentativas e o tempo limite de cada uma não são informados, por isso o handler deve responder o mais rápido possível.
Existe webhook para cobrança expirada?
Não. Os eventos de cobrança são transaction.pending, transaction.paid, transaction.refunded e transaction.refund_failed. Para saber que uma cobrança expirou, consulte o status pela API.
Posso liberar só os IPs da xFlow no firewall?
A documentação não publica os IPs de origem das entregas. Em vez de filtrar por IP, libere o caminho do webhook e confie na assinatura HMAC-SHA256 do header x-webhook-signature para recusar o que não veio da xFlow.
Como saber se o problema está do lado da xFlow?
Consulte a página de status em app.xflowpayments.com/status, que mostra a situação dos webhooks, e repita o teste no sandbox. Se o evento simulado chega, a entrega funciona e a causa está na configuração ou no seu handler.