Pular para o conteúdo principal

Catálogo de eventos do webhook — NFS-e Inbound

A NFE.io envia HTTP POST ao endpoint configurado em webhookUrl da empresa sempre que algo relevante acontece no subsistema NFS-e Inbound. Este documento cataloga os 6 eventos disponíveis, o shape do envelope, a política de retry e os mecanismos de segurança e idempotência.

Para desenvolvedores cliente que vão implementar o handler do webhook. Para visão sistêmica do fluxo, leia Arquitetura primeiro.

Sumário

Política de entrega

  • Entrega: at-least-once — seu handler deve ser idempotente (ver seção dedicada).
  • Method: POST com Content-Type: application/json; charset=utf-8.
  • Timeout: 30 segundos para sua resposta.
  • Retry: até 50 tentativas em 24 horas com backoff exponencial (30s → 60s → 120s → ... → max 7200s) e jitter de ±20%.
  • Códigos: 2xx confirma entrega; 4xx (exceto 408/429) marca DefinitivelyFailed sem retry; 408/429/5xx ou timeout disparam retry.
  • Assinatura: header x-hub-signature com HMAC-SHA1 do body bruto, formato sha1=<HEX> — ver Validação HMAC.

Após esgotar as tentativas, o documento permanece consultável via API. Reenvie manualmente com POST /v2/companies/{companyId}/inbound/nfse/{id}/resend-webhook.

Eventos

Todos os payloads são embrulhados num envelope { "body": { ... } }. Os exemplos abaixo mostram apenas o conteúdo de body para clareza.

Evento (body.eventName)Quando disparaNSU novo?
inbound.serviceInvoice.receivedNFS-e, DPS, EventRegistrationRequest, CNC ou documento desconhecido capturado do ADNSim
inbound.serviceInvoice.event.receivedEvento de NFS-e (cancelamento, ciência, rejeição, ato de ofício) capturado do ADNSim
inbound.manifestation.submittedResultado da sua submissão de Ciência/Rejeição (callback assíncrono)Não
inbound.company.deactivatedEmpresa desativada automaticamente (circuit breaker)Não
inbound.document.failedFalha no processamento de um documento (XML corrompido, parse error)Não
inbound.company.eventEventos operacionais (rate limit, reativação, consolidação)Não
Vocabulário atualizado em 2026-05-08

Os campos body.action (legacy snake_case issued_successfully / event_raised_successfully) continuam sendo enviados para retrocompatibilidade. Novas integrações devem filtrar por body.eventName (dotted). O type emite serviceInvoice para documentos e serviceInvoiceEvent para eventos (esse vocabulário não varia por versão do payload — veja Versionamento de webhook para o que de fato muda entre v1 e v2).

inbound.serviceInvoice.received

Disparado quando um documento NFS-e, DPS, EventRegistrationRequest, CNC ou unknown é capturado do ADN. O type (na raiz do envelope) discrimina a variante.

{
"body": {
"eventName": "inbound.serviceInvoice.received",
"action": "issued_successfully",
"type": "serviceInvoice",
"companyId": "54244e0ee340420fdc94ad10",
"accountId": "acc-123",
"document": {
"id": "66b2c3d4e5f6a7890bcdef12",
"direction": "Received",
"nsu": 12345,
"accessKey": "35250612345678000190000001234567890123451234",
"generatedOn": "2026-05-13T14:42:15Z",
"issuedOn": "2026-05-13",
"provider": { "federalTaxNumber": "12345678000190", "name": "Fornecedor LTDA", "cityCode": "3550308", "state": "SP" },
"borrower": { "federalTaxNumber": "98765432000110", "name": "Sua Empresa LTDA", "cityCode": "3106705", "state": "MG" },
"servicesAmount": 1500.00,
"federalServiceCode": "010501",
"cityServiceCode": null,
"amounts": { "deductionsAmount": 0, "amountNet": 1500.00 },
"taxes": { "issqn": { "base": 1500.00, "rate": 2.00, "amount": 30.00 } },
"environment": "Production",
"xmlUrl": "https://api.nfse.io/v2/.../inbound/nfse/66b2.../xml",
"pdfUrl": "https://api.nfse.io/v2/.../inbound/nfse/66b2.../pdf"
}
}
}

Idempotência: (body.companyId, body.document.nsu) ou body.document.id.

inbound.serviceInvoice.event.received

Disparado quando um evento de NFS-e é capturado (cancelamento, ciência, rejeição, ato de ofício). Carrega type = "serviceInvoiceEvent" (na raiz do envelope) e os campos eventCode (tpEvento raw) e eventType (legível, pode vir null para códigos novos).

{
"body": {
"eventName": "inbound.serviceInvoice.event.received",
"action": "event_raised_successfully",
"type": "serviceInvoiceEvent",
"companyId": "54244e0ee340420fdc94ad10",
"document": {
"id": "69f3f9f0731bd9db773ba624",
"eventType": "Cancellation",
"eventCode": "101101",
"nsu": 12346,
"accessKey": "35250612345678000190...",
"generatedOn": "2026-05-13T15:00:00Z",
"environment": "Production",
"xmlUrl": "https://api.nfse.io/v2/.../inbound/nfse/69f3.../xml",
"pdfUrl": null
}
}
}

A tabela completa de eventCode está em Códigos de evento — atenção aos dois contextos distintos (XSD tpEvento vs CSV EventCode do bulk export).

Campos fiscais sempre null em eventos: provider, borrower, servicesAmount, amounts, taxes, pdfUrl. Para obter esses dados, consulte a NFS-e hospedeira via document.accessKey.

inbound.manifestation.submitted

Callback do fluxo assíncrono de manifestação. Disparado quando o evento de Ciência (203202) ou Rejeição (203206) que você submeteu via API atinge estado terminal no SEFIN Nacional.

{
"body": {
"eventName": "inbound.manifestation.submitted",
"companyId": "54244e0ee340420fdc94ad10",
"manifestation": {
"id": "69e193685b7241b5c4fe8e3c",
"accessKey": "35503081223301943000745000000002734526036434454892",
"eventCode": 203202,
"reasonCode": null,
"status": "Accepted",
"submittedAt": "2026-04-17T02:47:08Z",
"acceptedAt": "2026-04-17T02:47:11Z",
"errorCode": null,
"errorMessage": null
}
}
}

Status terminais: Accepted, Rejected, Failed. Quando Rejected/Failed, errorCode traz o cStat SEFIN (135 OK, 217 NFS-e não consta, 573 duplicidade etc.) e errorMessage o xMotivo.

inbound.company.deactivated

Disparado quando uma empresa é desativada automaticamente pelo circuit breaker da NFE.io. Ação imediata recomendada — sem reativação, nenhum documento novo será capturado.

{
"body": {
"eventName": "inbound.company.deactivated",
"companyId": "54244e0ee340420fdc94ad10",
"company": {
"id": "54244e0ee340420fdc94ad10",
"name": "Empresa Teste LTDA",
"isActive": false,
"deactivationReason": "CertificateExpired",
"deactivatedAt": "2026-04-05T10:00:00Z"
}
}
}

deactivationReason possíveis: CertificateNotFound, CertificateExpired, CertificateRejected, CompanyNotAuthorized. Veja Troubleshooting para causa e ação recomendada de cada um.

inbound.document.failed

Disparado quando o processamento de um documento falha de forma irrecuperável (XML corrompido na origem, root element desconhecido, GZip inválido).

{
"body": {
"eventName": "inbound.document.failed",
"companyId": "54244e0ee340420fdc94ad10",
"document": {
"id": "6621a3f2e340420fdc000002",
"type": "None",
"nsu": 43,
"accessKey": null,
"generatedOn": "2026-04-05T10:01:00Z",
"environment": "Production",
"xmlUrl": null,
"pdfUrl": null
}
}
}

O XML não fica armazenado para documentos com falha. Casos recorrentes para o mesmo companyId indicam problema sistêmico — abra chamado no suporte com o companyId e nsu.

inbound.company.event

Notificações operacionais de baixa prioridade (rate limit ativado, reativação automática, consolidação de lote). Use para monitoramento — não exige ação na maioria dos casos.

{
"body": {
"eventName": "inbound.company.event",
"companyId": "54244e0ee340420fdc94ad10",
"eventType": "RateLimited",
"occurredAt": "2026-04-17T04:00:00Z",
"description": "ADN retornou 429 com Retry-After=3600; captura pausada até 2026-04-17T05:00:00Z."
}
}

Schema do objeto document

Para type=serviceInvoice (NFS-e autorizada), os blocos amounts, taxes e taxes.ibsCbs vêm populados:

BlocoCampos principaisObservações
document.amountsdiscountUnconditionedAmount, discountConditionedAmount, deductionsAmount, municipalBenefit{type,amount}, reimbursement, amountNetComponentes monetários não-tributários. Só discountUnconditionedAmount e deductionsAmount reduzem a BC do ISSQN.
document.taxes.issqnsituationCode, retentionType, base, rate, amountISSQN municipal. retentionType=1 prestador recolhe, 2 retido pelo tomador, 3 retido pelo intermediário.
document.taxes.federalirAmountWithheld, inssAmountWithheld, pisDueAmount, cofinsDueAmount, socialContributions{retentionType,withheldAmount}NT007/2026: pisDueAmount/cofinsDueAmount são DEVIDOS (não retidos). Retenções consolidadas em socialContributions.withheldAmount.
document.taxes.ibsCbssituationCode, classCode, basis, ibs.{state,municipal,totalAmount}, cbs.{rate,effectiveRate,amount}Reforma Tributária do Consumo. IBS repartido UF+Município. Vem null em NFS-e pré-RTC.

Garantia de shape: campos top-level do document aparecem sempre, mesmo quando null (parser strongly-typed seguro). Esta garantia não se estende a sub-campos — valide o pai antes de acessar (if (doc.amounts) { doc.amounts.amountNet }).

Validação HMAC

A NFE.io assina cada POST com HMAC-SHA1 sobre o body bruto. Validar a assinatura é obrigatório — sem isso, qualquer um que descubra a URL do seu endpoint pode forjar requisições.

ItemValor
Headerx-hub-signature (lowercase)
AlgoritmoHMAC-SHA1
Encodinghex MAIÚSCULO, sem separadores
Formatosha1=<HEX> (prefixo obrigatório, 45 chars total)
Conteúdo assinadoBody bruto UTF-8, exatamente como recebido
SecretString ASCII de 32 a 64 caracteres, configurada na subscrição

Vetor de teste

Secret: SuperSecretWebhookKey12345678901
Body: {"event":"test","id":"abc123","amount":42.00}
→ Header: sha1=502BC91DE70F6802FC16CD2E599A9AF752064FE5

Se sua implementação não gera exatamente esse hash com esses inputs, ela tem um bug — corrija antes de testar contra webhook real.

Exemplo (Python/Flask)

import hmac, hashlib
from flask import request, abort

WEBHOOK_SECRET = b"<seu-segredo-32-a-64-chars>"

@app.post("/webhook/nfse")
def receive():
sig = request.headers.get("x-hub-signature", "")
if not sig.startswith("sha1="):
abort(401)
expected = "sha1=" + hmac.new(
WEBHOOK_SECRET, request.get_data(cache=True), hashlib.sha1
).hexdigest().upper()
if not hmac.compare_digest(expected, sig):
abort(401)
# processar request.json["body"]
return "", 200

Use comparação timing-safe (hmac.compare_digest em Python, crypto.timingSafeEqual em Node, CryptographicOperations.FixedTimeEquals em C#) — comparação byte-a-byte vaza informação por análise de tempo.

Documentação completa

A NFE.io mantém o catálogo canônico de validação HMAC em nfe/shared-webhook-api/docs/webhook-signature-validation.md (PT/EN/ES, 13 seções). Lá você encontra:

  • 5 implementações de referência completas (Node.js, Python, PHP, C#, Go)
  • Passo-a-passo de validação com comparação em tempo constante
  • Troubleshooting (10 causas reais de falha em produção)
  • Política de rotação do secret com janela de transição
  • Headers adicionais que acompanham o webhook (Content-MD5, X-Hook-Id, X-Hook-Attempts, X-Hook-Event)
Não confunda autenticação com integridade

x-hub-signature (HMAC) autentica — prova que o webhook veio da NFE.io e não foi adulterado.

Content-MD5 apenas detecta corrupção em trânsito — qualquer atacante pode calcular o MD5 do payload forjado dele. MD5 ≠ autenticação.

Compatibilidade legada

Integrações em Version = raw-payload ou v1 recebem também o header X-NFEIO-Signature com formato sha1=<base64> (HMAC-SHA1 em base64, mantido para retrocompatibilidade). Novas integrações devem usar exclusivamente x-hub-signature.

Idempotência

Webhooks podem chegar mais de uma vez após retry. Seu handler deve deduplicar:

  • Para documentos (serviceInvoice.received, serviceInvoice.event.received, document.failed): use o par (body.companyId, body.document.nsu) ou body.document.id como chave única no seu banco.
  • Para manifestações (manifestation.submitted): use body.manifestation.id.
  • Para eventos de empresa (company.deactivated, company.event): use (body.companyId, body.occurredAt) ou aceite duplicação benigna (apenas re-aplique o estado).

Não use nsu puro como chave — o NSU é único por empresa, não globalmente. Sempre combine com companyId.

Boas práticas adicionais:

  • Responda rápido: retorne 2xx em até 5 segundos e processe assincronamente (fila interna).
  • Persista o payload antes de processar — não perca dados em crash do consumer.
  • Baixe XML/PDF cedo — URLs assinadas no xmlUrl/pdfUrl expiram em 30 minutos.
  • Monitore HTTP 5xx no seu endpoint — detecte problemas antes que o orçamento de retry esgote.

Veja também

NFE.io

A NFE.io é uma empresa de tecnologia que fornece soluções para automatizar e simplificar a emissão e gestão de notas fiscais eletrônicas. Com suas ferramentas, as empresas podem economizar tempo e reduzir erros, aumentando a eficiência e precisão do processo de emissão de notas fiscais.

Um dos principais cases de sucesso da NFE.io é a implementação da solução na empresa de transporte Rodonaves. Com a automatização da emissão e gestão de notas fiscais eletrônicas, a Rodonaves conseguiu reduzir em até 80% o tempo gasto nesse processo, o que se traduziu em uma significativa melhoria na eficiência operacional. Além disso, a empresa também conseguiu eliminar erros e atrasos na emissão de notas fiscais, o que melhorou a relação com seus clientes e aumentou a confiança dos órgãos fiscais.

Outro exemplo é a implementação da NFE.io na empresa de comércio eletrônico, a Loja Integrada. Com a automatização da emissão de notas fiscais, a Loja Integrada conseguiu aumentar a velocidade de emissão de notas em até 10 vezes, o que permitiu que a empresa atendesse a uma maior quantidade de clientes e, consequentemente, aumentar as suas vendas.

Além desses exemplos, a NFE.io também tem outros cases de sucesso com empresas de setores como indústria, construção, varejo e serviços, mostrando a versatilidade e eficácia da sua solução.

Em resumo, a NFE.io é uma empresa de tecnologia que oferece soluções para automatizar e simplificar a emissão e gestão de notas fiscais eletrônicas, ajudando as empresas a economizar tempo e reduzir erros, melhorando a eficiência e precisão do processo. Com cases de sucesso em diferentes setores, a NFE.io tem se destacado como uma empresa líder em automação fiscal.