---
title: "Payloads dos webhooks de emissão (NFS-e, NF-e, NFC-e)"
description: "Referência do corpo entregue em cada evento de emissão: envelope por tipo de documento, campos, exemplos reais anonimizados e como discriminar sucesso de falha."
source_url: https://nfe.io/docs/documentacao/webhooks/payloads-de-emissao
last_updated: 2026-07-30
---

# Payloads dos webhooks de emissão

Esta página documenta **o que chega no corpo** da requisição quando a NFE.io notifica seu endpoint sobre uma emissão. Para o cadastro do webhook, veja [Como cadastrar](./como-cadastrar-consultar-listar-editar-e-excluir.md); para o conceito geral, [Conceitos](./conceitos.md).

:::info Fonte dos exemplos
Os exemplos desta página são **entregas reais de produção**, com dados sensíveis substituídos. CNPJ, CPF e chaves de acesso foram trocados por valores fictícios **com dígito verificador válido** — servem para testar parsing e validação de DV, mas não correspondem a documentos existentes. Valores monetários, alíquotas, códigos fiscais, datas e a estrutura são fiéis ao original.
:::

## 1. A regra que quebra integrações: dois envelopes

O corpo entregue **não tem a mesma forma para todos os produtos**. Esta é a diferença mais importante desta página:

| Tipo de evento | Envelope | Onde ficam os campos |
|---|---|---|
| `service_invoice` (NFS-e) | **`{"payload": { ... }}`** | dentro de `payload` |
| `product_invoice` (NF-e) | achatado | na **raiz** do corpo |
| `consumer_invoice` (NFC-e) | achatado | na **raiz** do corpo |
| `*_inbound`, `product_tax`, `tax_payment_form` | achatado | na **raiz** do corpo |

Ou seja: para NFS-e você acessa `body.payload.status`; para NF-e/NFC-e, `body.status`.

:::warning Escreva o parser tolerante aos dois formatos
Se você assina mais de um tipo de evento no mesmo endpoint, normalize antes de processar:

```javascript
// Funciona para NFS-e (envelopado) e NF-e/NFC-e (achatado)
const documento = body.payload ?? body;
```

Fazer isso desde o início evita retrabalho quando você passar a emitir outro tipo de documento.
:::

## 2. Como descobrir qual evento chegou

Combine o cabeçalho com o corpo — os dois carregam informações diferentes:

| Origem | O que traz | Exemplo |
|---|---|---|
| Cabeçalho `X-Hook-Event` | o **tipo** do evento | `service_invoice` |
| Cabeçalho `X-Hook-Id` | identificador único da entrega (use para deduplicar) | `4efa03baf6014b788e591da82efbaba8` |
| Cabeçalho `X-Hook-Attempts` | número da tentativa | `1` |
| Corpo (`flowStatus` / `status`) | o **resultado** da operação | `Issued`, `IssueFailed`, `Error` |

:::tip Não confie no corpo para saber o tipo
`X-Hook-Event` traz o tipo do evento (`service_invoice`), não o par tipo + ação. Para saber o que aconteceu, leia `flowStatus` (NFS-e) ou `status` (NF-e/NFC-e) no corpo.
:::

## 3. NFS-e — `service_invoice`

Envelope: **`{"payload": { ... }}`**. Terminologia própria: o emissor é `provider` e o tomador é `borrower`.

### 3.1. Emissão bem-sucedida

```json
{
  "payload": {
    "id": "6a6898a87864720001efa2dc",
    "externalId": "e671beec-254b-4952-a046-430df91b2d2f",
    "environment": "Production",
    "flowStatus": "Issued",
    "status": "Issued",
    "provider": {
      "name": "EMPRESA EXEMPLO LTDA",
      "federalTaxNumber": "09505320001905",
      "municipalTaxNumber": "111111111",
      "address": {
        "postalCode": "20040020",
        "street": "Rua Exemplo",
        "number": "100",
        "district": "Centro",
        "city": { "code": "3304557", "name": "Rio de Janeiro" },
        "state": "RJ",
        "country": "BRA"
      },
      "type": "LegalPerson, Company"
    },
    "borrower": {
      "name": "EXEMPLO DISTRIBUIDORA SA",
      "federalTaxNumber": "34332984000120",
      "address": {
        "postalCode": "74915-240",
        "city": { "code": "5201405", "name": "Aparecida de Goiânia" },
        "state": "GO",
        "country": "BRA"
      },
      "type": "LegalPerson"
    },
    "number": 2905,
    "rpsNumber": 68521,
    "rpsSerialNumber": "1",
    "rpsType": "Rps",
    "rpsStatus": "Normal",
    "taxationType": "WithinCity",
    "cityServiceCode": "002",
    "federalServiceCode": "170102",
    "nbsCode": "118061000",
    "servicesAmount": 1620.0,
    "baseTaxAmount": 1620.0,
    "issRate": 0.05,
    "issTaxAmount": 81.0,
    "pisAmountWithheld": 10.53,
    "cofinsAmountWithheld": 48.6,
    "irAmountWithheld": 24.3,
    "csllAmountWithheld": 16.2,
    "inssAmountWithheld": 0.0,
    "issAmountWithheld": 0.0,
    "retentionType": "notWithheld",
    "amountWithheld": 99.63,
    "amountNet": 1520.37,
    "issuedOn": "2026-07-28T08:55:20-03:00",
    "createdOn": "2026-07-28T11:55:20.9045977+00:00",
    "apiVersion": 2
  }
}
```

Campos que a maioria das integrações usa:

| Campo | Tipo | Para que serve |
|---|---|---|
| `id` | string | Identificador da nota na NFE.io. Use para consultar via API. |
| `externalId` | string | O seu identificador, informado na emissão. Melhor chave para casar com o seu pedido. |
| `flowStatus` | string | Estado do fluxo: `Issued`, `IssueFailed`, `Cancelled`… |
| `status` | string | Estado da nota: `Issued`, `Error`, `Cancelled`. |
| `environment` | string | `Production` ou `Development`. **Sempre confira** antes de gravar em produção. |
| `number` | number | Número da NFS-e. `0` quando a emissão falhou. |
| `rpsNumber` / `rpsSerialNumber` | number / string | Número e série do RPS. |
| `amountNet` | number | Valor líquido após retenções. |

:::caution `documentUrl` e `documentXmlUrl` não são links públicos
Quando presentes, esses campos podem vir com o esquema interno `b2://`, que **não é acessível pelo seu sistema**. Para obter PDF e XML, use os endpoints da API REST da nota.
:::

### 3.2. Falha na emissão

Mesmo envelope; o que muda é o par `flowStatus` + `flowMessage`:

```json
{
  "payload": {
    "id": "6a636c5a7864720001d754b3",
    "externalId": "CUSTOMER_MONTHLY_SERVICE_FEE#CUSTOMER#00000000-0000-4000-8000-000000000000",
    "environment": "Development",
    "flowStatus": "IssueFailed",
    "flowMessage": "max retry reached on send batch stage",
    "status": "Error",
    "number": 0,
    "rpsNumber": 2190,
    "rpsSerialNumber": "IO",
    "cityServiceCode": "5895",
    "federalServiceCode": "15.10",
    "servicesAmount": 9.18,
    "issRate": 0.02,
    "issTaxAmount": 0.1836,
    "amountNet": 9.18,
    "approximateTax": {
      "source": "IBPT/empresometro.com.br",
      "version": "21.1.F",
      "totalRate": 0.1829,
      "totalAmount": 1.679022
    },
    "apiVersion": 2
  }
}
```

Note que `number` vem `0` e `status` vem `Error`. A causa legível fica em **`flowMessage`** — registre esse campo no seu log, é o que o suporte pede primeiro.

## 4. NF-e — `product_invoice`

Envelope **achatado**. Terminologia: `issuer` (emitente) e `buyer` (destinatário).

```json
{
  "id": "3d4c1b2a5f6e7d8c9b0a1f2e3d4c5b6a",
  "serie": 8,
  "number": 25969,
  "status": "Issued",
  "authorization": {
    "accessKey": "35260739425723000178550080000259691818677728"
  },
  "operationNature": "Outras Entradas - Retorno Simbólico",
  "operationType": "Incoming",
  "environmentType": "Production",
  "purposeType": "Normal",
  "issuer": {
    "name": "MODELO SERVICOS LTDA",
    "federalTaxNumber": 31305761000185,
    "taxRegime": "LucroReal",
    "address": {
      "postalCode": "16204393",
      "city": { "code": "3506508", "name": "BIRIGUI" },
      "state": "SP",
      "country": "BRA"
    },
    "type": "LegalEntity"
  },
  "buyer": {
    "name": "EXEMPLO DISTRIBUIDORA SA",
    "federalTaxNumber": 32308042000180,
    "stateTaxNumberIndicator": "TaxPayer",
    "address": {
      "city": { "code": "3518800", "name": "Guarulhos" },
      "state": "SP",
      "country": "BRA"
    },
    "type": "LegalEntity"
  },
  "totals": {
    "icms": {
      "baseTax": 78.56,
      "icmsAmount": 14.14,
      "productAmount": 78.56,
      "invoiceAmount": 78.56
    },
    "ibsCbs": {
      "basis": 64.42,
      "ibs": {
        "state":     { "amount": 0.06 },
        "municipal": { "amount": 0.00 },
        "totalAmount": 0.06
      },
      "cbs": { "amount": 0.58 }
    }
  },
  "transport": { "freightModality": "Free" },
  "payment": [
    { "paymentDetail": [ { "method": "WithoutPayment", "amount": 0 } ] }
  ],
  "lastEvents": {
    "events": [
      {
        "type": "Authorized",
        "sequence": 5,
        "data": {
          "accessKey": "35260739425723000178550080000259691818677728",
          "description": "Autorizado o uso da NF-e",
          "protocolNumber": "135262973194611",
          "statusCode": 100,
          "environmentType": "Production"
        }
      },
      {
        "type": "WebHooksDispatched",
        "sequence": 8,
        "data": { "action": "product_invoice.issued_successfully" }
      }
    ],
    "hasMore": false
  },
  "apiVersion": 2
}
```

### 4.1. Onde está a chave de acesso

A `accessKey` aparece em **dois lugares** e ambos são válidos:

1. `authorization.accessKey` — o caminho direto, prefira este.
2. `lastEvents.events[]` no item de `type: "Authorized"`, em `data.accessKey`.

```javascript
const chave =
  body.authorization?.accessKey ??
  body.lastEvents?.events?.find(e => e.type === "Authorized")?.data?.accessKey;
```

### 4.2. `lastEvents` é o histórico da nota

O array traz o caminho percorrido, cada item com `type` e `sequence` (ordem crescente de acontecimento). Tipos observados em produção:

| `type` | Significado |
|---|---|
| `DefinedNumberAndSerieSuccessfully` | Número e série atribuídos |
| `InvoiceSetAccessKey` | Chave de acesso calculada |
| `InvoiceXmlSigned` | XML assinado |
| `Authorized` | **Autorizada pela SEFAZ** — traz `protocolNumber` e `statusCode` |
| `Merged` | XML final consolidado |
| `SendSignedBatchFailed` | Falha no envio do lote (transiente; a nota pode seguir e autorizar) |
| `WebHooksDispatched` | Registro do próprio disparo de webhook |

:::tip `SendSignedBatchFailed` no histórico não significa nota com erro
É comum ver essa entrada em notas **autorizadas com sucesso** — houve uma falha transiente e o reenvio funcionou. Considere sempre o `status` da raiz, não a presença de um evento de falha no histórico.
:::

## 5. NFC-e — `consumer_invoice`

Mesma estrutura da NF-e (envelope achatado, `issuer`/`buyer`, `totals`, `lastEvents`). A diferença prática é o **destinatário pessoa física**:

```json
{
  "id": "8a7b6c5d4e3f2a1b0c9d8e7f6a5b4c3d",
  "serie": 2,
  "number": 1572,
  "status": "Issued",
  "authorization": {
    "accessKey": "51260771924245000153650020000015721152114412"
  },
  "operationNature": "Venda de mercadorias",
  "operationType": "Outgoing",
  "environmentType": "Production",
  "issuer": {
    "name": "TESTE LOGISTICA SA",
    "federalTaxNumber": 71924245000153,
    "taxRegime": "SimplesNacional",
    "type": "LegalEntity"
  },
  "buyer": {
    "name": "Maria Silva",
    "federalTaxNumber": "50465923046",
    "email": "cliente@exemplo.com.br",
    "stateTaxNumberIndicator": "NonTaxPayer",
    "address": {
      "postalCode": "78360000",
      "city": { "code": "5102637", "name": "Campo Novo do Parecis" },
      "state": "MT",
      "country": "BRA",
      "phone": "11999999999"
    },
    "type": "NaturalPerson"
  },
  "totals": {
    "icms": {
      "productAmount": 22.9,
      "freightAmount": 7.5,
      "discountAmount": 5.72,
      "othersAmount": 0.99,
      "invoiceAmount": 25.67
    }
  },
  "payment": [
    {
      "paymentDetail": [
        { "method": "Others", "methodDescription": "Marketplace Online", "amount": 25.67 }
      ],
      "payBack": 0
    }
  ],
  "apiVersion": 2
}
```

:::warning O corpo pode conter dados pessoais
Em NFC-e o `buyer` costuma ser `type: "NaturalPerson"`, com **CPF, e-mail e telefone**. Trate o corpo como dado pessoal: não registre em log aberto, restrinja acesso e observe sua política de retenção (LGPD).
:::

## 6. Regras de serialização que afetam seu parser

Comportamentos reais do serializador — considere todos ao escrever o código:

1. **Campos nulos são omitidos.** A chave simplesmente não vem. Nunca use "chave existe" como sinal; use acesso seguro (`?.`, `.get()`).
2. **O tipo de `federalTaxNumber` varia.** Em NF-e/NFC-e costuma vir **número** (`31305761000185`); em NFS-e, **string**. Normalize para string antes de comparar, e cuidado com zero à esquerda: um CNPJ que começa com `0` perde o dígito se tratado como inteiro.
3. **`number` também varia** entre string e inteiro conforme o produto.
4. **Datas em ISO 8601 com offset**, às vezes com milissegundos (`2026-07-28T11:55:20.9045977+00:00`). Não presuma resolução de segundos.
5. **Enums chegam como string** (`"Production"`), não como número.
6. **Campos novos podem aparecer sem aviso.** Ignore desconhecidos em vez de falhar — os campos `ibsCbs` da reforma tributária, por exemplo, foram adicionados a payloads já existentes.
7. **Não presuma ordem de chaves.**

## 7. Sucesso da entrega ≠ sucesso fiscal

O erro mais comum nesta integração:

| Pergunta | Onde responder |
|---|---|
| "Recebi a notificação?" | **status HTTP que você retorna** |
| "A nota foi emitida?" | **`flowStatus` / `status` no corpo** |

Um evento `issued_failed` é uma entrega **bem-sucedida** de uma notícia ruim: responda `2xx`, e registre a falha internamente pelo `flowMessage`.

:::danger Não responda 5xx porque a nota deu erro
Retornar erro HTTP faz a NFE.io reenviar o mesmo evento repetidamente. Responda `2xx` ao receber e trate o estado fiscal no seu sistema.
:::

## 8. Checklist de integração

- [ ] Normaliza o envelope (`body.payload ?? body`) para aceitar NFS-e e NF-e/NFC-e.
- [ ] Valida a assinatura HMAC antes de processar — veja [Dúvidas frequentes](./duvidas-frequentes.md).
- [ ] Deduplica por `X-Hook-Id` (reentrega é esperada).
- [ ] Responde `2xx` rápido e processa de forma assíncrona.
- [ ] Trata `_successfully`, `_failed` e `_error`.
- [ ] Confere `environment` / `environmentType` antes de gravar em produção.
- [ ] Acesso seguro a todo campo opcional (nulos são omitidos).
- [ ] Normaliza `federalTaxNumber` e `number` para string.
- [ ] Ignora campos desconhecidos.
- [ ] Não registra CPF/e-mail/telefone de NFC-e em log aberto.

## Próximos passos

- [Payloads dos webhooks de documentos recebidos](./payloads-de-entrada.md) — NF-e, CT-e e NFS-e capturadas de terceiros.
- [Dúvidas frequentes](./duvidas-frequentes.md) — validação de assinatura e cabeçalhos.
- [IPs de origem](./ips-de-origem.md) — allowlist de firewall.
