Skip to main content
Webhooks são entregues por endpoint configurado na empresa, assinados com HMAC-SHA256, de forma assíncrona — a entrega HTTP nunca bloqueia a requisição nem a transição de estado.
Webhooks cobrem a NF-e (eventos nfe_*). A NFS-e é acompanhada por polling em GET /notas/{id} — veja o Ciclo de vida.

Verificação de assinatura

Cada entrega traz dois cabeçalhos:
CabeçalhoConteúdo
X-Webhook-SignatureO HMAC-SHA256 em hexadecimal.
X-Webhook-TimestampInstante da assinatura (epoch Unix, em segundos).
A string canônica assinada é o X-Webhook-Timestamp, um ponto (.) e o corpo bruto do POST, concatenados:
"{timestamp}.{body}"
Recompute o HMAC-SHA256 dessa string com a chave_seguranca do endpoint e compare com X-Webhook-Signature usando uma comparação de tempo constante (ex.: Rack::Utils.secure_compare) — nunca uma igualdade simples de string, que vaza informação por timing. O timestamp na string assinada também permite rejeitar entregas antigas (proteção contra replay).
require "openssl"
require "rack/utils"

def webhook_valido?(corpo_bruto, timestamp, assinatura, chave_seguranca)
  esperado = OpenSSL::HMAC.hexdigest("SHA256", chave_seguranca, "#{timestamp}.#{corpo_bruto}")
  Rack::Utils.secure_compare(esperado, assinatura)
end

Semântica de entrega

A entrega é não ordenada e at-least-once (pelo menos uma vez): há um job independente por endpoint, com retries independentes. Portanto:
  • a ordem entre eventos não é garantida (um nfe_autorizada rápido pode chegar antes de um nfe_solicitacao_autorizacao que sofreu retry);
  • o mesmo evento pode ser entregue mais de uma vez.
Cada payload carrega event_id (UUID estável entre retries) e occurred_at (instante do evento). Os consumidores devem:
1

Deduplicar por event_id

Ignore um event_id já processado.
2

Ignorar eventos mais antigos

Descarte eventos com occurred_at anterior ao último já aplicado para aquela nota.

Tipos de evento (nfe_*)

EventoQuando dispara
nfe_solicitacao_autorizacaonota criada, autorização enfileirada
nfe_autorizadaveredito terminal: autorizada pela SEFAZ
nfe_rejeitadaveredito terminal: rejeitada pela SEFAZ
nfe_denegadaveredito terminal: denegada pela SEFAZ
nfe_solicitacao_cancelamentopedido de cancelamento enviado
nfe_cancelamento_autorizadocancelamento aceito pela SEFAZ
nfe_cancelamento_rejeitadocancelamento rejeitado (a nota segue autorizada)
nfe_inutilizadafaixa/nota inutilizada com sucesso

nfe_inutilizada tem dois formatos de payload

O evento nfe_inutilizada cobre os dois modos de inutilização, com payloads distintos — diferencie pela presença dos campos:
  • Modo nota — carrega uuid e chave_acesso (uma nota específica).
  • Modo faixa — carrega numero_inicial e numero_final (uma faixa de numeração, sem nota associada).
{
  "event_id": "5f3c0c2e-1b9a-4e8c-9a1d-2b6f0c7d8e90",
  "occurred_at": "2026-06-08T13:20:31Z",
  "uuid": "8e6d9a2b-4c1f-4a7e-bb3d-1f2a9c0d4e55",
  "chave_acesso": "23260612345678000190550010000000011000000017",
  "numero": 1,
  "serie": 1,
  "situacao": "autorizada",
  "protocolo": "123456789012345",
  "xml_url": "https://invo.work/api/nfe/v1/notas/8e6d.../xml",
  "danfe_url": "https://invo.work/api/nfe/v1/notas/8e6d.../danfe"
}
As URLs de xml_url / danfe_url são as rotas autenticadas da API (Basic auth), nunca links públicos a documentos fiscais.