---
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/
product: documentacao
last_updated: 2026-10-07
tags: ["webhook", "catalogo", "eventos", "nfse"]
---

# 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 no formato `{"action": "...", "payload": {...}}`: a ação do evento em `action` e a nota 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 — até 16 tentativas ao longo de ~45 h, exceto nos códigos de recusa definitiva. Veja [os números da política de entrega](../catalogo-de-eventos.md#quantas-tentativas-e-em-que-intervalo).
- **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 distinção chega pronta no campo `action` do corpo: `issued_error` ou `issued_failed`. Use esse campo.

Para referência, a NFE.io aplica 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.

No cancelamento, a regra tem um passo antes: se o cancelamento falhou e a nota **continua emitida** (`status: "Issued"`) — o caso comum de recusa da prefeitura —, o evento é sempre `cancelled_failed`, qualquer que seja o `flowMessage`. Nota já com `status: "Cancelled"` sempre gera `cancelled_successfully`. Nos demais status vale a regra do texto: `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 distinção entre `issued_error` e `issued_failed` chega pronta no campo `action` do corpo. O cabeçalho `X-Hook-Event` traz só o tipo (`service_invoice`).
:::

## Eventos de emissão

### `service_invoice.issued_successfully`

**Quando dispara:** a NFS-e foi emitida e autorizada pela prefeitura.

**Payload:**

```json
{
  "action": "issued_successfully",
  "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
  }
}
```

Em NFS-e, o `federalTaxNumber` de `provider` e de `borrower` chega como **número** quando o valor é numérico e a nota foi criada pela API v1/v2 — é o caso deste exemplo (`apiVersion: 2`). Chega como **string** quando o valor é alfanumérico ou quando a nota foi criada pela API v3.

Como número, o zero à esquerda some: o CNPJ `09505320001905` chega como `9505320001905`. Normalize para string com zeros à esquerda antes de comparar: 14 dígitos para CNPJ, 11 para CPF. 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
{
  "action": "issued_error",
  "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 chega pronta no campo `action` do corpo. Ela segue o prefixo `"max retry"` de `flowMessage`, nunca o `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
{
  "action": "issued_failed",
  "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
{
  "action": "cancelled_successfully",
  "payload": {
    "flowStatus": "Cancelled",
    "status": "Cancelled",
    "rpsStatus": "Cancelled"
  }
}
```

**Idempotency key:** `payload.id`.

### `service_invoice.cancelled_error`

**Quando dispara:** o cancelamento falhou por um erro pontual (não é esgotamento de retry) e a nota não está com `status` `Issued` nem `Cancelled`. A recusa comum da prefeitura, com a nota ainda emitida, chega como `cancelled_failed` (veja abaixo).

**Payload:** mesmo shape base, com:

```json
{
  "action": "cancelled_error",
  "payload": {
    "flowStatus": "CancelFailed",
    "flowMessage": "Falha de comunicacao com o webservice da prefeitura",
    "status": "Error"
  }
}
```

O exemplo é ilustrativo: `status` diferente de `Issued` e de `Cancelled` é o que separa este evento do `cancelled_failed` e do `cancelled_successfully`.

**Idempotency key:** `payload.id`.

### `service_invoice.cancelled_failed`

**Quando dispara:** o cancelamento falhou e a nota continua emitida (`status: "Issued"`) — por exemplo, a prefeitura recusou o pedido —, ou a NFE.io esgotou as tentativas de comunicar o cancelamento (`flowMessage` começando com `"max retry"`).

**Payload:** mesmo shape base, com:

```json
{
  "action": "cancelled_failed",
  "payload": {
    "flowStatus": "CancelFailed",
    "flowMessage": "Prazo de cancelamento expirado junto a prefeitura",
    "status": "Issued"
  }
}
```

Note que `status` permanece `Issued` — o cancelamento falhou, a nota continua válida.

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