Catálogo de eventos de webhook — NFS-e
Esta página documenta os 7 eventos de webhook do eventType service_invoice. O corpo vem sempre envelopado em {"payload": {...}} — veja Payloads de emissão para a regra geral dos dois envelopes.
Diferente de NF-e e NFC-e, o payload de NFS-e não tem array lastEvents. O histórico de tentativas não é exposto — você recebe apenas flowStatus e, em erro, flowMessage.
Política de entrega
- Entrega: at-least-once. Garanta idempotência por
X-Hook-Id. - Retry: reentrega automática em falha de rede ou resposta não-2xx.
- Timeout: responda 2xx rápido e processe de forma assíncrona.
- Assinatura: valide o HMAC do cabeçalho antes de processar. Veja Dúvidas frequentes.
Como issued_error e issued_failed se distinguem
A NFE.io usa uma regra simples e literal: se flowMessage começa com o texto "max retry", o evento é issued_failed — a emissão esgotou o número de tentativas. Qualquer outro texto em flowMessage gera issued_error — uma rejeição pontual da prefeitura ou uma falha de comunicação isolada.
A mesma regra vale para cancelamento: flowMessage começando com "max retry" gera cancelled_failed; qualquer outro erro gera cancelled_error.
flowStatusflowStatus é o mesmo (Error ou IssueFailed/CancelFailed) nos dois casos. A diferenciação em issued_error vs. issued_failed está só no cabeçalho X-Hook-Event/no corpo publicado — leia o texto de flowMessage se seu processo interno precisar da granularidade.
Eventos de emissão
service_invoice.issued_successfully
Quando dispara: a NFS-e foi emitida e autorizada pela prefeitura.
Payload:
{
"payload": {
"id": "d4911190b46ba44f",
"externalId": "seu-id-externo",
"environment": "Production",
"flowStatus": "Issued",
"provider": {
"tradeName": "Atacado Ferreira & Filhos LTDA",
"taxRegime": "SimplesNacional",
"specialTaxRegime": "MicroempresaMunicipal",
"legalNature": "SociedadeEmpresariaLimitada",
"municipalTaxNumber": "44338200330345",
"issRate": 0.0,
"id": "265f492ca6f35591",
"name": "Atacado Ferreira & Filhos LTDA",
"federalTaxNumber": "44338200330345",
"address": {
"street": "Avenida Central",
"number": "955",
"city": { "code": "3550308", "name": "Sao Paulo" },
"state": "SP",
"postalCode": "39257-113",
"country": "BRA"
},
"status": "Active",
"type": "LegalPerson, Company"
},
"borrower": {
"id": "654b17b903ade39e",
"name": "Carlos Eduardo Lima",
"federalTaxNumber": "30817158650",
"email": "[email protected]",
"address": {
"street": "Rua Sete de Setembro",
"number": "291",
"city": { "code": "3304557", "name": "Rio de Janeiro" },
"state": "RJ",
"postalCode": "87858-233",
"country": "BRA"
},
"status": "Active",
"type": "NaturalPerson"
},
"apiVersion": 2,
"issuedOn": "2026-08-17T21:46:14-03:00",
"number": 6909,
"status": "Issued",
"rpsType": "Rps",
"rpsStatus": "Normal",
"taxationType": "WithinCity",
"rpsSerialNumber": "ZZ",
"rpsNumber": 3610,
"cityServiceCode": "5771",
"federalServiceCode": "15.01",
"servicesAmount": 70.00,
"baseTaxAmount": 70.00,
"issRate": 0.02,
"issTaxAmount": 0.0,
"amountNet": 70.00
}
}
O campo federalTaxNumber de provider chega como número, não string — normalize antes de comparar. Campos nulos são omitidos, não vêm como null.
Idempotency key: payload.id ou o cabeçalho X-Hook-Id.
service_invoice.issued_error
Quando dispara: a prefeitura rejeitou a nota, ou houve falha pontual de comunicação — não é esgotamento de retry.
Payload: mesmo shape de issued_successfully, com:
{
"payload": {
"flowStatus": "Error",
"flowMessage": "Rejeicao da prefeitura: RPS ja processado anteriormente",
"status": "Error"
}
}
Idempotency key: payload.id.
service_invoice.issued_failed
Quando dispara: a NFE.io esgotou as tentativas de comunicação com o webservice da prefeitura. flowMessage sempre começa com "max retry".
Payload: mesmo shape de issued_successfully, com:
{
"payload": {
"flowStatus": "IssueFailed",
"flowMessage": "max retry: falha na comunicacao com o webservice da prefeitura apos numero maximo de tentativas",
"status": "IssueFailed"
}
}
Idempotency key: payload.id.
Eventos de cancelamento
service_invoice.cancelled_successfully
Quando dispara: o cancelamento foi homologado pela prefeitura.
Payload: mesmo shape de issued_successfully, com:
{
"payload": {
"flowStatus": "Cancelled",
"status": "Cancelled",
"rpsStatus": "Cancelled"
}
}
Idempotency key: payload.id.
service_invoice.cancelled_error
Quando dispara: a prefeitura rejeitou o pedido de cancelamento — por exemplo, prazo expirado. Não é esgotamento de retry.
Payload: mesmo shape base, com:
{
"payload": {
"flowStatus": "CancelFailed",
"flowMessage": "Rejeicao da prefeitura: prazo de cancelamento expirado",
"status": "Issued"
}
}
Note que status permanece Issued — o cancelamento falhou, a nota continua válida.
Idempotency key: payload.id.
service_invoice.cancelled_failed
Quando dispara: a NFE.io esgotou as tentativas de comunicar o cancelamento à prefeitura. flowMessage começa com "max retry".
Payload: mesmo shape base, com:
{
"payload": {
"flowStatus": "Error",
"flowMessage": "Prazo de cancelamento expirado junto a prefeitura",
"status": "Issued"
}
}
Idempotency key: payload.id.
Evento sem exemplo observado: pulled
pulled existe no contrato de eventos de NFS-e, mas não teve nenhuma ocorrência registrada em 180 dias de produção até a publicação desta página. Não documentamos payload de exemplo para não apresentar uma estrutura hipotética como real. Se você assinar este evento e receber uma entrega, entre em contato — vamos atualizar esta página com o caso real.
Como validar a assinatura
Veja o exemplo de validação de HMAC em Dúvidas frequentes.