Skip to main content
Quando algo acontece com o seu dinheiro, a gente faz um POST na sua URL com o evento em JSON. É assim que você sabe que o PIX caiu sem ficar consultando a API.

Duas configurações diferentes

URLs da conta, no painel

Até cinco, configuradas em API e Desenvolvedores. São o destino padrão de todo evento da conta.Se você só quer um endereço para tudo, configure uma e ignore o resto.

callbackUrl, por cobrança

Você manda no corpo da requisição ao criar. Vale só para aquela cobrança, e tem prioridade sobre as URLs da conta.Use quando cobranças diferentes precisam avisar sistemas diferentes.
As duas não somam: quando a cobrança tem callbackUrl, o evento vai para ela. As URLs da conta preenchem o que ficou sem destino, não duplicam o que já tem.
Com mais de uma URL da conta, cada uma recebe uma cópia do evento e tem a própria fila de reentrega. Um endereço fora do ar não atrasa nem cancela a entrega nos outros, e a tela de entregas mostra qual deles falhou.Todas recebem a mesma assinatura e o mesmo X-Webhook-Id. Se dois dos seus sistemas gravam no mesmo banco, deduplique por esse header nos dois.

O que cada URL precisa ter

URL inválida devolve 400 na criação da cobrança, com callbackUrl must be a valid, public HTTPS URL. O erro vem na hora, não na entrega.
Em desenvolvimento, use um túnel (ngrok http 3000, cloudflared tunnel) para expor o seu localhost com HTTPS público. Passar http://localhost:3000 não funciona por design.

Headers

X-Webhook-Signature vem em todo webhook, sem opt-in. Se você não valida, a sua URL aceita qualquer POST da internet dizendo que um pagamento foi aprovado.

Reentrega

Se a entrega falhar (timeout, erro de rede ou status fora de 2xx), o evento entra na fila e é reenviado. São até 7 tentativas, cobrindo pouco mais de 20 horas. O corpo reenviado é idêntico ao original e o X-Webhook-Id não muda.
É exatamente por causa da reentrega que o seu handler precisa ser idempotente. Deduplique pelo X-Webhook-Id antes de fazer qualquer coisa que não dá pra desfazer, como creditar saldo ou enviar licença.

Eventos

Todos caem nas URLs da conta. A coluna abaixo diz quem consegue desviar disso. Em sandbox, a marca de teste entra depois do prefixo da família: os eventos de saque saem como withdrawal.test.requested e withdrawal.test.completed. Pagamento de código tem família própria (pix_payment.*) em vez de reusar withdrawal.*: quem já concilia saques não passa a receber pagamento no mesmo balde. O identificador acompanha a família: o corpo traz paymentId, que é o mesmo payment.id devolvido pelo pagamento.

Corpo dos eventos de contestação

Os quatro dispute.* mandam o mesmo corpo, mudando o event e o status:
previousStatus vem null na primeira vez que avisamos daquela contestação. Use o code para casar com o que aparece no painel.

O jeito certo de tratar

1

Leia o corpo cru

Antes de qualquer parse de JSON. A assinatura é calculada sobre os bytes exatos que chegaram.
2

Valide a assinatura

Com comparação timing-safe. Se não bater, responda 401 e pare por aí.
3

Deduplique pelo X-Webhook-Id

Já processou esse id? Responda 200 e não faça nada.
4

Grave e responda 200

Salve o evento e responda imediatamente. O processamento pesado vai para uma fila.
5

Confira o valor antes de entregar

amountCents precisa bater com o que você esperava. Status paid sozinho não basta.
Não conte com a ordem de chegada. Se a ordem importa para você, confirme o estado atual com um GET na API antes de decidir.

Validando a assinatura

Código pronto em Node, PHP, Python e Go.