# Webhooks de nota fiscal: como saber na hora que a NFS-e foi autorizada

> Por que a emissão de nota é assíncrona, quais eventos acompanhar, como verificar a assinatura (Standard Webhooks), tratar repetições e montar um receptor confiável.

Fonte: https://sendnf.com/blog/webhooks-de-nota-fiscal/ · Publicado: 2026-10-19 · SendNF

**Webhooks** são chamadas que o SendNF faz para o seu sistema quando algo muda numa nota: autorizada, rejeitada, bloqueada por falta de dado, cancelada. Eles existem porque a emissão é **assíncrona**: a autorização depende da Receita, da prefeitura ou da SEFAZ, que podem responder em segundos ou ficar fora do ar por horas. Em vez de consultar a nota de tempos em tempos, você cadastra uma URL e recebe um POST assinado no padrão **Standard Webhooks** a cada mudança. Verifique a assinatura, use o `webhook-id` para não processar o mesmo evento duas vezes e responda 2xx rápido.

## Por que a nota não é "na hora"

Mesmo quando a nota sai em segundos, o caminho tem etapas que você não controla: a fila, a assinatura, a transmissão, a resposta do órgão. Quando o órgão cai:

- na **NFS-e**, a nota fica pendente e é reenviada até a autorização;
- na **NFC-e**, a nota sai em **contingência** e é transmitida quando a SEFAZ volta.

Um sistema que espera a resposta na mesma requisição ou trava o usuário ou perde a nota. Com webhooks, você responde ao usuário na hora ("nota em emissão") e atualiza quando o evento chegar. Para fluxos interativos, o SendNF também aceita `?wait=true`, que espera até cerca de 25 segundos, mas o webhook continua sendo a fonte da verdade.

## Os eventos

| Evento | Quando |
|---|---|
| `document.created` | A nota foi criada e entrou na fila |
| `document.authorized` | A nota foi autorizada |
| `document.rejected` | A nota foi rejeitada; o motivo vem explicado |
| `document.blocked` | Falta um dado da empresa (certificado, inscrição…) |
| `document.contingency` | NFC-e emitida em contingência |
| `document.cancelled` | O cancelamento foi registrado |
| `document.cancel_denied` | O órgão recusou o cancelamento |
| `certificate.expiring` | O certificado A1 vence em 30, 15, 7 ou 1 dia |
| `certificate.expired` | O certificado A1 venceu |

Notas `blocked` voltam para a fila sozinhas quando o dado chega; você recebe `document.authorized` depois.

## O formato

```http
POST /webhooks/sendnf HTTP/1.1
webhook-id: 7c1d9a52-3f0e-4c1b-9d6e-2a8f4b7e1c90
webhook-timestamp: 1792512000
webhook-signature: v1,K5oZfzN95Z9UVu1EsfQmfVNQhnkZ2pj9o9NDN/H/pI4=

{
  "id": "7c1d9a52-3f0e-4c1b-9d6e-2a8f4b7e1c90",
  "type": "document.authorized",
  "mode": "live",
  "created_at": "2026-10-20T14:32:05.120Z",
  "data": { "id": "…", "object": "nfse", "status": "authorized", "reference": "pedido-1042", "nfse_number": "1042" }
}
```

O campo `data` é a nota no mesmo formato da API, com a sua `reference`, o número e os links do PDF e do XML. Webhooks de teste e de produção são separados: um endpoint cadastrado com chave de teste só recebe eventos de homologação.

## Verificando a assinatura

A assinatura segue o padrão Standard Webhooks: HMAC-SHA256 com o segredo do endpoint (`whsec_…`) sobre `id.timestamp.corpo`, em base64, prefixado por `v1,`. Com o SDK:

```ts
import { verifyWebhook } from '@sendnf/sdk';

const ok = await verifyWebhook({
  secret: process.env.SENDNF_WEBHOOK_SECRET!,
  id: req.header('webhook-id')!,
  timestamp: req.header('webhook-timestamp')!,
  signature: req.header('webhook-signature')!,
  body: rawBody,
});
```

Dois cuidados que derrubam muita integração:

1. **Use o corpo bruto**, exatamente como chegou. Se o seu framework fizer o parse do JSON antes, a assinatura não bate.
2. **Recuse timestamps velhos** (a função rejeita mais de 5 minutos de diferença), para evitar replay.

## Um receptor confiável em 5 regras

1. **Verifique a assinatura** antes de qualquer coisa.
2. **Responda 2xx rápido** e processe em segundo plano. Respostas lentas viram reenvio.
3. **Deduplique pelo `webhook-id`**: o mesmo evento pode chegar mais de uma vez.
4. **Não dependa da ordem**: use o `status` da nota em `data` como verdade, não a sequência dos eventos.
5. **Use a `reference`** para achar o pedido no seu sistema; ela é sua.

## Reenvio e depuração

Se o seu endpoint falhar, o SendNF reenvia com espera crescente. No painel (e pela API) você vê cada entrega, o status HTTP recebido, o erro e pode **reenviar** manualmente. Para testar o receptor, dispare um evento de teste pelo painel ou por `POST /v1/webhooks/test`.

Por segurança, em produção os webhooks só vão para URLs HTTPS que apontem para endereços públicos.

## Reconciliação: o plano B dos webhooks

Mesmo com reenvio, trate os webhooks como a forma rápida de saber, não a única. Uma rotina de reconciliação, rodando algumas vezes por dia, fecha as lacunas:

1. Liste as notas que o seu sistema ainda considera pendentes.
2. Consulte cada uma no SendNF (`GET /v1/documents?reference=…` ou pelo id).
3. Atualize o status local a partir da resposta.

Com isso, um deploy mal feito no seu receptor, ou uma janela de indisponibilidade maior que o período de reenvio, não deixa nota sem status. A `reference` que você envia na emissão é o elo entre os dois lados: use o id do pedido, da venda ou da fatura.

## Resumo

- A emissão é assíncrona; webhooks avisam quando o status muda.
- Eventos de nota (criada, autorizada, rejeitada, bloqueada, cancelada) e de certificado.
- Assinatura Standard Webhooks: verifique com o corpo bruto e o segredo `whsec_`.
- Responda rápido, deduplique pelo `webhook-id` e confie no `status` da nota.

## Perguntas frequentes

### Por que usar webhook em vez de consultar a nota?

Porque a autorização depende de sistemas externos (Receita, prefeitura, SEFAZ) e pode levar de segundos a horas quando eles estão fora do ar. O webhook avisa o seu sistema no momento em que o status muda, sem consultas repetidas.

### Como verificar se o webhook veio mesmo do SendNF?

Pela assinatura no header webhook-signature, calculada com HMAC-SHA256 sobre o id, o timestamp e o corpo, no padrão Standard Webhooks. O SDK do SendNF tem a função verifyWebhook.

### O que acontece se o meu servidor estiver fora do ar?

O SendNF reenvia o evento com espera crescente entre as tentativas. Também dá para reenviar manualmente pelo painel ou pela API.

### Posso receber o mesmo evento duas vezes?

Sim, como em qualquer sistema de webhooks com reenvio. Use o webhook-id para ignorar eventos já processados.

### Quantos endpoints de webhook posso cadastrar?

Você pode cadastrar mais de um endpoint por modo (teste ou produção) e escolher os eventos de cada um, por exemplo um para o financeiro e outro para o suporte. Cada endpoint tem o próprio segredo de assinatura.

