Webhooks são URLs HTTP do seu servidor que o Brasil NFe chama automaticamente quando algo acontece nas suas notas. Em vez de você ficar consultando a API em loop, é o Brasil NFe que avisa você — enviando um POST com o payload em JSON.
Use webhooks para integrar com ERP, sistema de logística, CRM, dashboards internos ou qualquer coisa que precise reagir a eventos fiscais em tempo (quase) real.
Cadastro pelo painel
O cadastro é feito em api.brasilnfe.com.br → Painel → Webhooks:
- Clique em Adicionar webhook.
- Informe um nome (ex.: "Integração ERP") e a URL que vai receber os POSTs (precisa ser HTTPS em produção).
- Salve. O secret é gerado uma única vez — copie e guarde no seu cofre de segredos. Se perder, use Regerar secret (a integração antiga deixa de funcionar imediatamente).
- Em Empresa → Credenciais, selecione qual webhook esta empresa específica utiliza.
Resolução por empresa
Para cada disparo, o Brasil NFe escolhe o webhook assim:
- Se a empresa tem um webhook explicitamente vinculado (campo
IdWebhook), ele é usado. - Senão, se existir exatamente um webhook ativo sob a mesma conta, ele é usado como fallback.
- Se houver mais de um ativo e nenhum explicitamente vinculado, nenhum disparo acontece — a configuração é ambígua.
O envelope do payload
Cada disparo é um POST com Content-Type: application/json e o seguinte envelope:
Code
| Campo | Descrição |
|---|---|
event | Nome do evento (ex.: nfe.lote.processado). Veja a tabela de eventos. |
deliveryId | Identificador único da tentativa de entrega. Use como chave de idempotência no seu lado. |
timestamp | ISO-8601 em UTC, momento em que o disparo foi montado. |
data | Carga específica do evento. |
Headers enviados
| Header | Conteúdo |
|---|---|
User-Agent | BrasilNFe-Webhooks/1.0 |
X-Webhook-Event | Nome do evento (mesmo valor de event no body). |
X-Webhook-Delivery | Mesmo valor de deliveryId. Idêntico em todas as tentativas do mesmo evento. |
X-Webhook-Timestamp | Mesmo valor de timestamp. Reflete o momento em que o disparo original foi montado, não a tentativa atual. |
X-Webhook-Signature | sha256=<hex> — HMAC-SHA256 do body bruto com o secret do webhook. |
X-Webhook-Attempt | Número da tentativa atual (1 no disparo original, 2..5 em retentativas). |
Verificação da assinatura
A assinatura é o HMAC-SHA256 do corpo bruto da requisição, codificada em hexadecimal e prefixada por sha256=. Sempre compare em tempo constante para evitar timing attacks.
Code
Importante. Calcule o HMAC sobre o body exatamente como recebido — sem reserializar o JSON. Frameworks que parseiam o body antes de você ler costumam reordenar chaves e quebram a assinatura. No Express, use
express.raw(); no ASP.NET, leia o stream antes do model binding.
Idempotência
Use o X-Webhook-Delivery (= deliveryId no body) como chave de idempotência com TTL de pelo menos 24h. Em caso de retentativas (veja abaixo) ou raros timeouts dos dois lados, o mesmo deliveryId pode chegar mais de uma vez — armazene-o e processe apenas a primeira ocorrência.
A idempotência via
deliveryIdé a defesa primária. O Brasil NFe garante que o mesmo evento sempre carrega o mesmodeliveryId, em todas as tentativas.
Janela anti-replay (defesa secundária)
Como o X-Webhook-Timestamp reflete o momento do disparo original, e retentativas podem chegar até ~1h12min depois, use uma janela de ±2 horas se quiser validar timestamp como camada adicional. A proteção primária contra replay deve ser a idempotência por deliveryId.
Resposta esperada
Seu endpoint deve responder 2xx em até 15 segundos. Qualquer outra coisa — erro HTTP (4xx/5xx), timeout, exceção de rede — marca a tentativa como falha. A entrega é então re-tentada automaticamente seguindo a tabela de backoff abaixo.
Se você precisa de garantia de entrega no seu lado, devolva 2xx imediatamente ao receber e processe de forma assíncrona do seu lado (fila interna). É o padrão recomendado.
Retentativas e backoff
O Brasil NFe faz até 5 tentativas de entrega para cada evento, com backoff exponencial:
| Tentativa | Quando dispara | Header X-Webhook-Attempt |
|---|---|---|
| 1 | Imediatamente, no momento do evento. | 1 |
| 2 | 30 segundos após a tentativa 1 falhar. | 2 |
| 3 | 2 minutos após a tentativa 2 falhar. | 3 |
| 4 | 10 minutos após a tentativa 3 falhar. | 4 |
| 5 | 1 hora após a tentativa 4 falhar. | 5 |
Tempo total máximo: ~1h12min entre o evento e a quinta (e última) tentativa. Após a quinta tentativa falhar, o evento é considerado perdido — mas todas as tentativas ficam registradas em Painel → Webhooks → Logs para auditoria.
O que se mantém entre tentativas
deliveryId— idêntico em todas as tentativas. Use para idempotência.event— idêntico.- Body completo — byte-a-byte idêntico (logo, a
X-Webhook-Signaturetambém é idêntica). X-Webhook-Timestamp— reflete o disparo original, não muda.
O que muda entre tentativas
X-Webhook-Attempt— incrementa de 1 a 5.- URL e secret — usam a configuração atual no momento da tentativa. Se você atualizou o webhook entre o disparo original e a retentativa, a próxima tentativa vai para a nova URL com o novo secret. Webhook desativado entre tentativas → não há mais retentativas.
Eventos de teste não são re-tentados
O evento test.ping (botão "Testar" no painel) nunca é re-tentado — é uma checagem manual e o resultado é mostrado no painel imediatamente.
Eventos suportados
| Evento | Quando dispara | Payload (data) |
|---|---|---|
test.ping | Botão "Testar" no painel. | { test: true, message, sentAt } |
nfe.lote.processado | Após o processamento bem-sucedido de um envio em lote via /EnviarNotaFiscalLote. | { tipoAmbiente, modeloDocumento, quantidade, notas[] } |
Mais eventos serão adicionados gradualmente. O nome do evento é estável — quando um evento entra na lista, o nome não muda.
Boas práticas de segurança
- Use uma URL dedicada ao webhook (ex.:
/webhooks/brasilnfe) — não compartilhe com outros endpoints públicos. - Sempre HTTPS em produção. Endereços HTTP planos serão chamados, mas o secret e o payload trafegam em claro.
- Valide a assinatura antes de qualquer parsing custoso ou consulta a banco.
- Guarde o secret em variável de ambiente ou cofre (Vault, AWS Secrets Manager, etc.), nunca no código.
- Se o secret vazou, regere imediatamente pelo painel — o secret antigo é invalidado na hora.
Teste local
Para testar sem precisar gerar uma nota real:
- No painel, abra Webhooks e clique no ícone de avião (Testar) ao lado do webhook.
- O Brasil NFe envia um POST com
event: "test.ping"e payload mínimo. - Veja o resultado imediatamente (HTTP status, duração) e o request/response completo em Logs.
Para desenvolvimento local atrás de NAT, use um túnel como ngrok ou localtunnel e cadastre a URL pública gerada.

