Webhooks
Em vez de consultar a nota até ela sair da fila, cadastre uma URL e receba o resultado.
{ "url": "https://seu-sistema.com.br/webhooks/emitza", "events": ["nfe.authorized", "nfe.rejected", "nfse.authorized", "nfse.rejected"] }Sem events, você recebe todos. A resposta traz o secret (whsec_...) uma única vez: guarde para conferir a assinatura. Até 5 webhooks por conta. POST /v1/webhooks/{id}/test envia um webhook.test para você conferir a integração.
O que chega
Um POST com JSON:
{
"id": "6710c3d4e5f6a7b8c9d0e1f2",
"event": "nfe.authorized",
"createdAt": "2026-10-15T14:03:11.204Z",
"data": {
"id": "6710c3d4e5f6a7b8c9d0e1aa",
"externalId": "pedido-1234",
"companyId": "6710a1b2c3d4e5f601234567",
"environment": "homologation",
"series": 1,
"number": 42,
"accessKey": "35261011222333000181550010000000421482917367",
"protocol": "135260000000042",
"authorizedAt": "2026-10-15T14:03:10.000Z"
}
}Cabeçalhos: X-Fiscal-Event (o evento), X-Fiscal-Delivery (o id da entrega) e X-Fiscal-Signature (a assinatura).
| Evento | Quando |
|---|---|
nfe.authorized, nfse.authorized |
Nota autorizada |
nfe.rejected, nfse.rejected |
Rejeitada, com o motivo |
nfe.denied |
Uso denegado |
nfe.failed, nfse.failed |
Parou sem resposta do órgão |
nfe.cancelled, nfse.cancelled |
Cancelamento registrado |
nfe.correction_registered |
Carta de correção registrada |
nfe.event_rejected, nfse.event_rejected |
Cancelamento, carta ou inutilização recusados |
nfe.disabled |
Faixa de números inutilizada |
certificate.expiring, certificate.expired |
Certificado vencendo (30, 15 e 7 dias antes) ou vencido |
webhook.test |
Teste manual |
O formato completo de cada evento está na referência.
Responda rápido e trate repetições
- Responda com qualquer status 2xx em até 10 segundos. Processe depois, numa fila sua.
- Sem 2xx, a emitza tenta de novo depois de 1 min, 5 min, 15 min, 1 h, 3 h, 6 h e 12 h (cerca de 22 horas no total).
- A mesma entrega pode chegar mais de uma vez. Use o
iddo corpo para ignorar a repetição. - A ordem de chegada não é garantida. Na dúvida, consulte
GET /v1/nfe/{id}.
Confira a assinatura
X-Fiscal-Signature tem o formato t=<segundos unix>,v1=<hex>, onde v1 é o HMAC-SHA256 de "<t>.<corpo>" com o seu secret. Use o corpo exatamente como chegou, antes de qualquer conversão de JSON, e recuse timestamps com mais de 5 minutos de diferença.
Node.js:
import { createHmac, timingSafeEqual } from 'node:crypto';
export function assinaturaValida(secret, corpoBruto, cabecalho) {
const partes = Object.fromEntries(String(cabecalho).split(',').map((p) => p.split('=', 2)));
const t = Number(partes.t);
if (!Number.isInteger(t) || !partes.v1 || Math.abs(Date.now() / 1000 - t) > 300) return false;
const esperado = createHmac('sha256', secret).update(`${t}.${corpoBruto}`).digest('hex');
return esperado.length === partes.v1.length && timingSafeEqual(Buffer.from(esperado), Buffer.from(partes.v1));
}PHP:
function assinaturaValida(string $secret, string $corpoBruto, string $cabecalho): bool {
parse_str(str_replace(',', '&', $cabecalho), $partes);
$t = (int) ($partes['t'] ?? 0);
if (!isset($partes['v1']) || abs(time() - $t) > 300) return false;
$esperado = hash_hmac('sha256', $t . '.' . $corpoBruto, $secret);
return hash_equals($esperado, $partes['v1']);
}No PHP, leia o corpo com file_get_contents('php://input').
Esta página em Markdown, para IA: index.md