---
title: "Catálogo de eventos de webhook — NFS-e"
description: "Todos os eventos de webhook de emissão e cancelamento de NFS-e (service_invoice), com payload real anonimizado."
source_url: https://nfe.io/docs/webhooks/catalogo-saida-nfse
last_updated: 2026-08-26
---

# 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](../guias/payloads-de-emissao.md) 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](../duvidas-frequentes.md).

## 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`.

:::tip Não tente inferir o tipo de erro pelo `flowStatus`
`flowStatus` é 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:**

```json
{
  "payload": {
    "id": "d4911190b46ba44f",
    "externalId": "seu-id-externo",
    "environment": "Production",
    "flowStatus": "Issued",
    "provider": {
      "tradeName": "Atacado Ferreira & Filhos LTDA",
      "taxRegime": "SimplesNacional",
      "specialTaxRegime": "MicroempresaMunicipal",
      "legalNature": "SociedadeEmpresariaLimitada",
      "companyRegistryNumber": 6202300,
      "regionalTaxNumber": 355030999,
      "municipalTaxNumber": "44338200330345",
      "issRate": 0.0,
      "id": "265f492ca6f35591",
      "name": "Atacado Ferreira & Filhos LTDA",
      "federalTaxNumber": 44338200330345,
      "email": "contato@atacadoferreira.example.com",
      "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": "contato842@example.com",
      "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:

```json
{
  "payload": {
    "flowStatus": "IssueFailed",
    "flowMessage": "[1001] XML não compatível com Schema. The 'CodigoServico' element is invalid - The value '040802.001' is invalid according to its datatype '...:tpCodigoServico' - The Pattern constraint failed.",
    "status": "Error"
  }
}
```

Mensagens de `issued_error` variam bastante — vêm de rejeições distintas da prefeitura ou de falha de validação de schema, como no exemplo acima. Trate `flowMessage` como texto livre para exibição/log, não como valor para parsing por padrão fixo.

:::tip `flowStatus` em `issued_error` é sempre `IssueFailed`
Mesmo quando a ação do webhook é `issued_error` (não `issued_failed`), o campo `flowStatus` no corpo aparece como `IssueFailed` — é o mesmo estado interno para os dois casos. A distinção entre as duas ações está só no texto de `flowMessage` (prefixo `"max retry"`) e no cabeçalho `X-Hook-Event`/path do evento, nunca em `flowStatus`.
:::

**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:

```json
{
  "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:

```json
{
  "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:

```json
{
  "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:

```json
{
  "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](../duvidas-frequentes.md) — 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](../duvidas-frequentes.md).

## Veja também

- [Catálogo de eventos — visão geral](../catalogo-de-eventos.md)
- [Payloads de emissão — regras gerais](../guias/payloads-de-emissao.md)
- [Catálogo de eventos de saída — NF-e](./catalogo-saida-nfe.md)
- [Catálogo de eventos de saída — NFC-e](./catalogo-saida-nfce.md)
