Pular para o conteúdo
emitzadocs

Webhooks

Em vez de consultar a nota até ela sair da fila, cadastre uma URL e receba o resultado.

POST /v1/webhooks
{ "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:

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

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:

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:

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