Pular para o conteúdo principal

Webhooks

Como a emissão é assíncrona por padrão, o status final chega no seu ERP via webhook HTTP POST com payload JSON e assinatura HMAC.

Configurar

  1. Portal Notare → WebhooksNovo endpoint
  2. URL HTTPS pública do seu ERP
  3. Escolher eventos de interesse
  4. Salvar o signing secret — usado pra validar a assinatura

Eventos

nfse.autorizadaevent
NFS-e foi autorizada pela prefeitura.
nfse.rejeitadaevent
Validação ou regra de negócio falhou. Veja erros[].
nfse.canceladaevent
Cancelamento confirmado.
nfse.substituidaevent
Substituição efetivada (nova nota emitida).
nfse.falha_definitivaevent
Esgotou retries automáticos sem sucesso.

Payload

POST {sua URL}
{
"event": "nfse.autorizada",
"timestamp": "2026-06-13T22:00:00Z",
"idIntegracao": "OS-30046339",
"nfse": {
"id": "5fcb9e1d-...",
"status": "autorizada",
"numero": "20119",
"chaveAcesso": "20119-7493120626...",
"numeroRps": "33472",
"serieRps": "13",
"urls": {
"pdf": "https://api.notare.nexosos.com/v1/nfse/5fcb9e1d-.../pdf",
"xml": "https://api.notare.nexosos.com/v1/nfse/5fcb9e1d-.../xml"
}
}
}
Use idIntegracao pra correlacionar

O idIntegracao está sempre no payload (quando você informou na emissão). Use-o como chave no seu ERP — você não precisa guardar o id UUID da Notare.

Headers do webhook

HeaderValor
Content-Typeapplication/json
User-AgentNotare-Webhook/1.0
X-Notare-EventEx: nfse.autorizada
X-Notare-Delivery-IdUUID único da entrega (útil pra log)
X-Notare-SignatureHMAC-SHA256 — ver abaixo

Validação HMAC

O header X-Notare-Signature traz o timestamp e a assinatura:

X-Notare-Signature: t=1718289600,v1=5257a869e7ecebeda32affa62cdca3fa...

Node.js

import crypto from 'crypto';

function validarAssinatura(rawBody, header, secret) {
const [tPart, v1Part] = header.split(',');
const timestamp = tPart.split('=')[1];
const assinatura = v1Part.split('=')[1];

// Rejeita webhooks com mais de 5 min (replay protection)
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;

const payload = `${timestamp}.${rawBody}`;
const esperada = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');

return crypto.timingSafeEqual(
Buffer.from(assinatura),
Buffer.from(esperada),
);
}

Python

import hmac, hashlib, time

def validar_assinatura(raw_body: bytes, header: str, secret: str) -> bool:
parts = dict(p.split('=', 1) for p in header.split(','))
timestamp = parts['t']
assinatura = parts['v1']

# Replay protection (5 min)
if abs(time.time() - int(timestamp)) > 300:
return False

payload = f"{timestamp}.{raw_body.decode()}".encode()
esperada = hmac.new(secret.encode(), payload, hashlib.sha256).hexdigest()

return hmac.compare_digest(assinatura, esperada)

Retry automático

Se seu endpoint responder com HTTP ≠ 2xx, a Notare retenta com backoff exponencial:

TentativaAtraso desde a anterior
1imediato
2+30s
3+2min
4+5min
5+30min
6+2h
7+6h
8+24h

Após 8 tentativas, marca como delivery_failed. Reentrega manual via portal ou POST /v1/webhooks/{id}/replay.

Idempotência no seu ERP

A Notare pode entregar o mesmo evento mais de uma vez (timeout do seu lado, retry, etc.). Trate idempotentemente:

async function handle(payload) {
const chave = `${payload.event}:${payload.nfse.id}`;
const jaProcessado = await db.eventos.findUnique({ where: { eventoId: chave } });
if (jaProcessado) return; // OK, ignora

// ... processa ...

await db.eventos.create({ data: { eventoId: chave } });
}

Boas práticas

  • Responda em < 5s — faça processamento pesado em fila depois de gravar recebimento
  • Log o X-Notare-Delivery-Id — suporte rastreia entregas por ele
  • Use HTTPS — webhooks HTTP são rejeitados em produção
  • IP allowlist (opcional) — webhooks saem de 212.85.0.117/32. Consulte a status page pra mudanças.