Webhooks
Catálogo de eventos, cabeçalhos de entrega, assinatura HMAC-SHA256, payloads por tipo de documento, retentativas e circuit breaker dos webhooks da TF Fiscal.
Modelo de entrega
A plataforma envia os resultados dos documentos e os veredictos de verificação ao seu servidor, sem necessidade de polling.
- Uma URL de callback por aplicação, configurada no console ("Configuração de webhook" nos detalhes da aplicação) ou por Registrar webhook. As duas formas gravam a mesma configuração e têm efeito idêntico; vale o último salvamento, e o formato e os cabeçalhos dos callbacks nunca dependem de onde a URL foi configurada. Hoje o console oferece apenas a assinatura de
invoice.verify.completed; assine os demais eventos comeventosno registro. Os resultados de NF-e, NFC-e, CT-e, DC-e e NFS-e e o veredicto da verificação de NF-e são todos entregues nessa URL. - O
tokende callback é opcional. Quando definido (no console ou no registro), todo callback o envia sem alteração nos cabeçalhostokenex-token, qualquer que seja o tipo de evento; sem token, nenhum dos dois é enviado. O console mostra apenas o valor mascarado, e o token pode ser alterado ou removido lá. Apenas caracteres ASCII visíveis (sem espaços), até 256. - Entrega ao menos uma vez (at-least-once). Uma retentativa após timeout pode chegar mesmo que a tentativa original tenha sido processada, e o mesmo evento pode ser distribuído a vários destinos. Deduplique pelo id do evento (cabeçalho
X-Tffiscal-Event-Ide, nos payloads com envelope,event_id): ele é idêntico em todas as tentativas do mesmo evento. - Cada entrega é um HTTP
POSTcom corpo JSON (Content-Type: application/json; charset=utf-8) para a URL registrada. - Documentos de teste e de produção seguem o mesmo contrato, a mesma assinatura e o mesmo cronograma de retentativas, veja Ambientes.
Catálogo de eventos
| Evento | Disparado quando | Payload |
|---|---|---|
invoice.authorized | NF-e autorizada pela SEFAZ (cStat 100) | Objeto de resultado NF-e, nfeStatus = Autorizada |
invoice.rejected | NF-e rejeitada pela SEFAZ | Objeto de resultado NF-e, nfeStatus = Negada, motivo em nfeMotivoStatus |
invoice.canceled | Cancelamento da NF-e registrado na SEFAZ (cStat 135) | Envelope de evento, data traz invoice_id / chave / protocolo |
invoice.cce.registered | Carta de correção (CC-e) registrada em uma NF-e | Envelope de evento, data traz invoice_id / chave / n_seq. Reservado: o registro da CC-e é síncrono hoje e este evento ainda não é entregue |
invoice.verify.completed | A verificação de NF-e chegou ao veredicto final (VALIDATED / REJECTED / VALIDATION_ERROR) | Envelope de evento com o veredicto em data |
cte.authorized | CT-e autorizado (cStat 100) | Objeto de resultado CT-e, cteStatus = Autorizada |
cte.rejected | CT-e rejeitado pela SEFAZ (cStat diferente de 100) | Objeto de resultado CT-e, cteStatus = Negada, cteMotivoStatus = cStat - motivo |
cte.canceled | Cancelamento do CT-e registrado (cStat 135) | Objeto de resultado CT-e, cteStatus = Cancelada |
cte.event.registered | Qualquer outro evento de CT-e registrado (carta de correção, comprovante de entrega, insucesso de entrega, desacordo de serviço e seus cancelamentos) | Envelope de evento, data traz event_code / n_seq / protocolo |
dce.authorized | DC-e autorizada (cStat 100) | Objeto de resultado DC-e, dceStatus = Autorizada |
dce.rejected | DC-e rejeitada pela SEFAZ, ou a tarefa de emissão falhou de forma terminal (inclusive antes de o documento ser numerado) | Objeto de resultado DC-e, dceStatus = Negada |
dce.canceled | Cancelamento da DC-e registrado (cStat 135 / 136 / 155) | Objeto de resultado DC-e, dceStatus = Cancelada |
dce.cancel_rejected | A SEFAZ rejeitou o cancelamento da DC-e, ou a tarefa de cancelamento falhou de forma terminal | Objeto de resultado DC-e, dceStatus = CancelamentoNegado, o documento continua autorizado |
nfce.authorized | NFC-e autorizada; também enviado quando uma nota offline é autorizada após a transmissão | Objeto de resultado NFC-e, nfeStatus = Autorizada |
nfce.rejected | NFC-e rejeitada pela SEFAZ, ou falhou no processamento da plataforma; também enviado quando uma nota offline é rejeitada na transmissão | Objeto de resultado NFC-e, nfeStatus = Negada |
nfce.canceled | Cancelamento da NFC-e (inclusive por substituição) registrado | Objeto de resultado NFC-e, nfeStatus = Cancelada |
nfce.cancel_rejected | A SEFAZ rejeitou o cancelamento da NFC-e | Objeto de resultado NFC-e, nfeStatus = CancelamentoNegado, a nota continua válida |
nfce.contingency_issued | NFC-e emitida offline com a SEFAZ indisponível | Objeto de resultado NFC-e, nfeStatus = ContingenciaPendente |
nfse.authorized | NFS-e emitida com sucesso | Objeto de resultado NFS-e, nfseStatus = Autorizada |
nfse.rejected | NFS-e rejeitada pela autoridade, ou falha do lado da plataforma | Objeto de resultado NFS-e, nfseStatus = Negada, motivo em nfseMotivoStatus |
nfse.canceled | Cancelamento da NFS-e concluído (inclusive análise deferida) | Objeto de resultado NFS-e, nfseStatus = Cancelada |
nfse.cancel_requested | Análise de cancelamento da NFS-e aceita | Objeto de resultado NFS-e, nfseStatus = EmProcessoDeCancelamento |
nfse.cancel_denied | O cancelamento da NFS-e não ocorreu e o documento continua autorizado | Objeto de resultado NFS-e, nfseStatus = Autorizada, motivo em nfseMotivoStatus |
nfse.substituted | NFS-e substituída por outro documento | Objeto de resultado NFS-e, nfseStatus = Substituida, mais nfseChaveSubstituta |
nfse.inbound.received | NFS-e recebida, sendo este emitente tomador / intermediário (exige assinatura explícita) | Notificação de NFS-e recebida |
nfse.inbound.canceled | NFS-e recebida foi cancelada (exige assinatura explícita) | Notificação de NFS-e recebida |
nfse.inbound.substituted | NFS-e recebida foi substituída (exige assinatura explícita) | Notificação de NFS-e recebida |
nfse.inbound.manifested | A manifestação (confirmação / rejeição) de uma NFS-e recebida foi registrada (exige assinatura explícita) | Envelope de evento, resultado em data |
nfse.inbound.manifest_failed | A tarefa de manifestação de uma NFS-e recebida falhou; manifeste de novo antes do prazo de confirmação tácita (exige assinatura explícita) | Envelope de evento, motivo da falha em data.motivo |
email.sent | E-mail da nota entregue ao serviço de envio (exige assinatura explícita) | Envelope de evento, data.status = Enviado |
email.failed | E-mail da nota falhou de forma definitiva: espera esgotada, tentativas esgotadas, nota que deixou de estar autorizada (exige assinatura explícita) | Envelope de evento, data.status = Falha, motivo em data.motivo_falha |
Há dois formatos de payload, escolhidos apenas pelo tipo de evento (última coluna acima), nunca por a URL ter sido configurada no console ou pelo endpoint de registro:
- Objeto de resultado do documento: objeto JSON simples cujo primeiro campo é
tipo(NF-e/NFC-e/CT-e/DC-e/NFS-e), com ordem fixa de campos. Usado pelos resultados de autorização e rejeição da NF-e, pelos cinco eventos da NFC-e (nfce.*) e pelos resultados de autorização, rejeição e cancelamento de CT-e, DC-e e NFS-e. Não possui campoevent_id; deduplique pelo cabeçalhoX-Tffiscal-Event-Id. - Envelope de evento:
{ "version", "event_id", "event_type", "occurred_at", "data" }. Usado porinvoice.canceled,invoice.cce.registered,invoice.verify.completed,cte.event.registered,nfse.inbound.*eemail.sent/email.failed. Os campos dentro dedatasó são adicionados, nunca renomeados ou removidos; uma mudança incompatível incrementaversion.
Nota: rejeições da SEFAZ nunca são erros HTTP da chamada de emissão. Elas aparecem como status
Negadana consulta e como eventoinvoice.rejected/cte.rejected/dce.rejected. Uma tarefa de CT-e que falha de forma terminal no lado da plataforma (Falha) não gera callback; use a consulta de CT-e para detectá-la.
Cabeçalhos da requisição
| Cabeçalho | Significado |
|---|---|
X-Tffiscal-Event | Código do evento, por exemplo invoice.authorized (webhook.verify na entrega de teste enviada ao salvar uma URL no console) |
X-Tffiscal-Event-Id | Id do evento, a chave de idempotência; não muda entre retentativas e é compartilhado por todos os destinos do mesmo evento |
X-Tffiscal-Delivery-Id | Identificador da entrega; não deduplique por ele, use o id do evento |
X-Tffiscal-Timestamp | Segundos Unix, gerado novamente a cada tentativa |
X-Tffiscal-Signature | hex( HMAC-SHA256( app_secret, timestamp + "." + body ) ), em minúsculas |
token | Token de callback opcional: o valor definido no console ou enviado em Registrar webhook, devolvido sem alteração. Ausente quando nenhum token está definido |
x-token | Mesmo valor de token, presente sempre que token estiver; verifique qualquer um dos dois |
Verificando a assinatura
Calcule HMAC-SHA256 sobre a string timestamp + "." + rawBody com o seu app_secret e compare em tempo constante com o cabeçalho X-Tffiscal-Signature. Use os bytes brutos recebidos: desserializar e serializar de novo o payload altera a ordem dos campos ou os espaços e invalida a assinatura. Rejeite entregas com timestamp antigo demais (5 minutos é uma tolerância razoável).
Verificar a assinatura é recomendado: ela comprova a origem e que o corpo não foi alterado. Se você definiu um token de callback, também pode comparar o cabeçalho token com ele.
Node.js:
const crypto = require('node:crypto');/*** Verifica uma entrega de webhook da TF Fiscal.* @param {string} secret seu app_secret* @param {string} timestamp valor do cabeçalho X-Tffiscal-Timestamp* @param {Buffer|string} rawBody corpo bruto da requisição, sem parse* @param {string} signature valor do cabeçalho X-Tffiscal-Signature* @returns {boolean} true quando a assinatura é autêntica*/function verifyWebhook(secret, timestamp, rawBody, signature) {const expected = crypto.createHmac('sha256', secret).update(timestamp + '.' + rawBody, 'utf8').digest('hex');return (expected.length === signature.length &&crypto.timingSafeEqual(Buffer.from(expected, 'utf8'), Buffer.from(signature, 'utf8')));}
Java:
import java.nio.charset.StandardCharsets;import java.security.MessageDigest;import javax.crypto.Mac;import javax.crypto.spec.SecretKeySpec;public final class WebhookVerifier {/*** Verifica uma entrega de webhook da TF Fiscal.** @param secret seu app_secret* @param timestamp valor do cabeçalho X-Tffiscal-Timestamp* @param rawBody corpo bruto da requisição, sem parse* @param signature valor do cabeçalho X-Tffiscal-Signature* @return true quando a assinatura é autêntica*/public static boolean verify(String secret, String timestamp, String rawBody, String signature) {try {Mac mac = Mac.getInstance("HmacSHA256");mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));byte[] digest = mac.doFinal((timestamp + "." + rawBody).getBytes(StandardCharsets.UTF_8));StringBuilder hex = new StringBuilder(digest.length * 2);for (byte b : digest) {hex.append(String.format("%02x", b));}return MessageDigest.isEqual(hex.toString().getBytes(StandardCharsets.UTF_8),signature.getBytes(StandardCharsets.UTF_8));} catch (Exception e) {return false;}}}
Payload de NF-e
Eventos invoice.authorized e invoice.rejected. Registrados em Registrar webhook; veja NF-e para o fluxo de emissão.
Autorizada:
{"tipo": "NF-e","empresaId": "1934811222334455","nfeId": "NFe-000014553","nfeStatus": "Autorizada","nfeLinkDanfe": "https://api.v2.tffiscal.com/openapi/files/danfe/35241204893402000113650010000117691017244265?token=MXw3fDEwMXwxNzU4...","nfeLinkXml": "https://api.v2.tffiscal.com/openapi/files/xml/8801?token=MXw3fDEwMXwxNzU4...","nfeNumero": "11769","nfeSerie": "10","nfeChaveAcesso": "35241204893402000113650010000117691017244265","nfeDataEmissao": "2024-12-04T17:44:26Z","nfeDataAutorizacao": "2024-12-04T17:44:26Z","nfeNumeroProtocolo": "135240002599237"}
Negada:
{"tipo": "NF-e","empresaId": "1934811222334455","nfeId": "NFe-000014553","nfeStatus": "Negada","nfeMotivoStatus": "778 - Rejeicao: NCM inexistente","nfeNumero": "11769","nfeSerie": "10","nfeChaveAcesso": "35241204893402000113650010000117691017244265","nfeDataEmissao": "2024-12-04T17:44:26Z"}
| Campo | Tipo | Descrição |
|---|---|---|
tipo | string | Sempre NF-e |
empresaId | string | Identificador da empresa |
nfeId | string | O id enviado na emissão |
nfeStatus | string | Autorizada / Negada |
nfeMotivoStatus | string | Motivo da negativa: código de status da SEFAZ + descrição; vazio quando autorizada |
nfeLinkDanfe | string | Link de download do PDF do DANFE (veja Download de arquivos; utilizável no momento do callback); vazio quando negada |
nfeLinkXml | string | Link de download do XML autorizado; vazio quando negada |
nfeNumero | string | Número da nota |
nfeSerie | string | Série |
nfeChaveAcesso | string | Chave de acesso de 44 dígitos |
nfeDataEmissao | string | Data de emissão, ISO-8601 UTC |
nfeDataAutorizacao | string | Data de autorização; vazio quando negada |
nfeNumeroProtocolo | string | Número do protocolo de autorização; vazio quando negada |
nfeDigestValue | string | Digest da assinatura, não fornecido no momento |
Campos sem valor aparecem no payload como valores vazios.
Cancelamento e carta de correção de NF-e
invoice.canceled usa o envelope de evento com data trazendo invoice_id (identificador da nota na plataforma), chave (chave de acesso de 44 dígitos) e protocolo (protocolo do evento de cancelamento). O cancelamento em si é confirmado de forma síncrona por Cancelar NF-e e pelo status Cancelada na consulta.
invoice.cce.registered está reservado no catálogo (data: invoice_id / chave / n_seq). O endpoint de registro da CC-e devolve o protocolo de forma síncrona e nenhum callback é entregue para ele no momento.
Payload de CT-e
Mesmo registro e mesmos cabeçalhos da NF-e; veja CT-e para o fluxo de emissão. O objeto de resultado usa tipo = CT-e com ordem fixa de campos.
| Evento | Disparo | cteStatus |
|---|---|---|
cte.authorized | Autorização 100 | Autorizada |
cte.rejected | Rejeição da SEFAZ (cStat diferente de 100) | Negada (cteMotivoStatus é cStat - motivo); uma falha terminal da tarefa (Falha) não gera callback, use a API de consulta |
cte.canceled | Cancelamento 135 | Cancelada |
cte.event.registered | Qualquer outro evento registrado | Envelope de evento, data traz event_code / n_seq / protocolo |
Autorizado:
{"tipo": "CT-e","empresaId": "1934811222334455","cteId": "CTE-ORD-1","cteStatus": "Autorizada","cteMotivoStatus": null,"cteLinkDacte": "https://api.v2.tffiscal.com/openapi/files/dacte/3526...?token=...","cteLinkXml": "https://api.v2.tffiscal.com/openapi/files/xml/7?token=...","cteNumero": "1","cteSerie": "1","cteChaveAcesso": "3526...","cteDataEmissao": "2026-09-06T12:00:00Z","cteDataAutorizacao": "2026-09-06T12:00:03Z","cteNumeroProtocolo": "135260000000001","cteDigestValue": "..."}
| Campo | Tipo | Descrição |
|---|---|---|
tipo | string | Sempre CT-e |
empresaId | string | Identificador da empresa |
cteId | string | O id enviado na emissão |
cteStatus | string | Autorizada / Negada / Cancelada |
cteMotivoStatus | string | null | cStat - motivo quando rejeitado; null nos demais casos |
cteLinkDacte | string | null | Link de download do PDF do DACTE; null quando rejeitado |
cteLinkXml | string | null | Link de download do XML autorizado; null quando rejeitado |
cteNumero | string | Número do documento |
cteSerie | string | Série |
cteChaveAcesso | string | Chave de acesso de 44 dígitos |
cteDataEmissao | string | Data de emissão, ISO-8601 UTC |
cteDataAutorizacao | string | null | Data de autorização; null quando rejeitado |
cteNumeroProtocolo | string | null | Número do protocolo de autorização; null quando rejeitado |
cteDigestValue | string | null | Digest da assinatura do XML autorizado |
cteLinkDacte e cteLinkXml são utilizáveis assim que o callback de autorização chega; os links não precisam de cabeçalhos de assinatura e redirecionam com 302, veja Download de arquivos.
Evento registrado (cte.event.registered), por exemplo um comprovante de entrega (110180):
{"version": "1.0","event_id": "987654321098765432","event_type": "cte.event.registered","occurred_at": "2026-09-06T13:00:00Z","data": {"event_code": "110180","n_seq": "1","protocolo": "135260000000099"}}
| Campo | Tipo | Descrição |
|---|---|---|
event_code | string | Código do evento na SEFAZ, por exemplo 110110 carta de correção, 110180 comprovante de entrega |
n_seq | string | Número de sequência do evento |
protocolo | string | Número do protocolo do evento |
Payload de DC-e
Mesmo registro e mesmos cabeçalhos da NF-e; veja DC-e para o fluxo de emissão. O objeto de resultado usa tipo = DC-e com 14 campos em ordem fixa.
| Evento | Disparo | dceStatus |
|---|---|---|
dce.authorized | Autorização 100 | Autorizada |
dce.rejected | Rejeição da SEFAZ ou falha terminal da tarefa (inclusive falhas antes de o documento ser numerado, que não têm chave) | Negada (dceMotivoStatus é cStat - motivo; uma falha terminal sem cStat traz apenas o motivo) |
dce.canceled | Cancelamento registrado (135 / 136 / 155) | Cancelada (dceDataAutorizacao é o momento do registro do cancelamento, dceNumeroProtocolo o protocolo do evento de cancelamento) |
dce.cancel_rejected | A SEFAZ rejeitou o cancelamento ou a tarefa de cancelamento falhou de forma terminal | CancelamentoNegado (o documento continua autorizado; traz o protocolo de autorização / digest / link do XML) |
Autorizada:
{"tipo": "DC-e","empresaId": "1934811222334455","dceId": "DCe-000012333","dceStatus": "Autorizada","dceMotivoStatus": null,"dceLinkDace": "https://api.v2.tffiscal.com/openapi/files/dace/35260940673061000134990010000000011100000016?token=...","dceLinkXml": "https://api.v2.tffiscal.com/openapi/files/xml/7?token=...","dceNumero": "1","dceSerie": "1","dceChaveAcesso": "41260940673061000134990010000000011101234565","dceDataEmissao": "2026-09-06T12:00:00Z","dceDataAutorizacao": "2026-09-06T12:00:03Z","dceNumeroProtocolo": "141260000000001","dceDigestValue": "tasG40Ic6Zh7PIBiLEEH7Hb940Y="}
Rejeitada:
{"tipo": "DC-e","empresaId": "1934811222334455","dceId": "DCe-000012333","dceStatus": "Negada","dceMotivoStatus": "225 - Rejeicao: Falha no schema XML","dceLinkDace": null,"dceLinkXml": null,"dceNumero": "1","dceSerie": "1","dceChaveAcesso": "4126...","dceDataEmissao": "2026-09-06T12:00:00Z","dceDataAutorizacao": null,"dceNumeroProtocolo": null,"dceDigestValue": null}
Falha antes da numeração (empresa não configurada para DC-e, série ausente, falha de mapeamento da mensagem e outras falhas terminais em que o documento nunca foi numerado e não tem chave): dce.rejected continua sendo entregue, os campos de fato do documento são null, dceMotivoStatus traz o motivo da falha e dceDataEmissao o momento da aceitação. Correlacione pelo dceId e nunca presuma que dceChaveAcesso está presente:
{"tipo": "DC-e","empresaId": "1934811222334455","dceId": "DCe-000012333","dceStatus": "Negada","dceMotivoStatus": "Empresa não configurada para emissão de DC-e","dceLinkDace": null,"dceLinkXml": null,"dceNumero": null,"dceSerie": null,"dceChaveAcesso": null,"dceDataEmissao": "2026-09-06T12:00:00Z","dceDataAutorizacao": null,"dceNumeroProtocolo": null,"dceDigestValue": null}
Cancelada:
{"tipo": "DC-e","empresaId": "1934811222334455","dceId": "DCe-000012333","dceStatus": "Cancelada","dceMotivoStatus": null,"dceLinkDace": "https://api.v2.tffiscal.com/openapi/files/dace/35260940673061000134990010000000011100000016?token=...","dceLinkXml": "https://api.v2.tffiscal.com/openapi/files/xml/7?token=...","dceNumero": "1","dceSerie": "1","dceChaveAcesso": "4126...","dceDataEmissao": "2026-09-06T12:00:00Z","dceDataAutorizacao": "2026-09-06T15:00:00Z","dceNumeroProtocolo": "141260000000099","dceDigestValue": null}
Cancelamento rejeitado:
{"tipo": "DC-e","empresaId": "1934811222334455","dceId": "DCe-000012333","dceStatus": "CancelamentoNegado","dceMotivoStatus": "594 - Rejeicao: O numero de sequencia do evento informado e maior que o permitido","dceLinkDace": null,"dceLinkXml": "https://api.v2.tffiscal.com/openapi/files/xml/7?token=...","dceNumero": "1","dceSerie": "1","dceChaveAcesso": "4126...","dceDataEmissao": "2026-09-06T12:00:00Z","dceDataAutorizacao": null,"dceNumeroProtocolo": "141260000000001","dceDigestValue": "tasG40Ic6Zh7PIBiLEEH7Hb940Y="}
| Campo | Tipo | Descrição |
|---|---|---|
tipo | string | Sempre DC-e |
empresaId | string | Identificador da empresa |
dceId | string | O id enviado na emissão; chave de correlação de todos os eventos, presente mesmo quando o documento nunca foi numerado |
dceStatus | string | Autorizada / Negada / Cancelada / CancelamentoNegado |
dceMotivoStatus | string | null | cStat - motivo em rejeição da SEFAZ; apenas o motivo em falha terminal sem cStat; null em sucesso |
dceLinkDace | string | null | Link de download do PDF do DACE; null quando rejeitada e em CancelamentoNegado |
dceLinkXml | string | null | Link de download do XML autorizado; null quando rejeitada |
dceNumero | string | null | Número do documento; null quando nunca numerado |
dceSerie | string | null | Série; null quando nunca numerado |
dceChaveAcesso | string | null | Chave de acesso de 44 dígitos; null quando nunca numerado |
dceDataEmissao | string | Data de emissão, ISO-8601 UTC (momento da aceitação em falha antes da numeração) |
dceDataAutorizacao | string | null | Data de autorização; momento do registro do cancelamento em Cancelada; null quando rejeitada |
dceNumeroProtocolo | string | null | Protocolo de autorização; protocolo do evento de cancelamento em Cancelada; null quando rejeitada |
dceDigestValue | string | null | Digest da assinatura do XML autorizado; null quando rejeitada e em Cancelada |
Os dois links seguem o formato da API de consulta (/openapi/files/{kind}/{ref}?token=...) e sua validade vem da configuração do tenant (7 dias por padrão), veja Download de arquivos.
Payload de NFC-e
Reaproveita a URL de callback e os cabeçalhos da NF-e; o fluxo de emissão está em NFC-e. Esses cinco eventos não fazem parte da assinatura padrão: liste-os em eventos no registro do webhook para que sejam entregues. O objeto de resultado tem os campos da NF-e com tipo = NFC-e e o conteúdo do QR Code em nfeQrCode; os 14 campos têm ordem fixa e aparecem mesmo vazios.
| Código do evento | Gatilho | nfeStatus |
|---|---|---|
nfce.authorized | Autorizada, ou autorizada após a transmissão de uma nota offline | Autorizada |
nfce.rejected | Rejeição da SEFAZ, falha no processamento da plataforma, ou rejeição de nota offline transmitida | Negada (nfeMotivoStatus traz código e motivo) |
nfce.canceled | Cancelamento ou cancelamento por substituição registrado | Cancelada (nfeDataAutorizacao é o momento do cancelamento, nfeNumeroProtocolo o protocolo do cancelamento) |
nfce.cancel_rejected | A SEFAZ rejeitou o cancelamento | CancelamentoNegado (a nota continua válida; motivo em nfeMotivoStatus) |
nfce.contingency_issued | Emitida offline com a SEFAZ indisponível | ContingenciaPendente (link do cupom e nfeQrCode, sem protocolo) |
Autorizada:
{"tipo": "NFC-e","empresaId": "1934811222334455","nfeId": "PDV01-000123","nfeStatus": "Autorizada","nfeMotivoStatus": null,"nfeLinkDanfe": "https://api.v2.tffiscal.com/openapi/files/danfce/35261111222333000181650010000001231123456780?token=MXw3fDEwMXwxNzU4...","nfeLinkXml": "https://api.v2.tffiscal.com/openapi/files/xml/9901?token=MXw3fDEwMXwxNzU4...","nfeNumero": "123","nfeSerie": "1","nfeChaveAcesso": "35261111222333000181650010000001231123456780","nfeDataEmissao": "2026-11-04T15:20:01Z","nfeDataAutorizacao": "2026-11-04T15:20:02Z","nfeNumeroProtocolo": "135261234567890","nfeQrCode": "https://www.homologacao.nfce.fazenda.sp.gov.br/qrcode?p=35261111222333000181650010000001231123456780|3|2"}
Rejeitada:
{"tipo": "NFC-e","empresaId": "1934811222334455","nfeId": "PDV01-000123","nfeStatus": "Negada","nfeMotivoStatus": "725 - Rejeicao: NFC-e com CFOP invalido","nfeLinkDanfe": null,"nfeLinkXml": null,"nfeNumero": "123","nfeSerie": "1","nfeChaveAcesso": "35261111222333000181650010000001231123456780","nfeDataEmissao": "2026-11-04T15:20:01Z","nfeDataAutorizacao": null,"nfeNumeroProtocolo": null,"nfeQrCode": null}
| Campo | Tipo | Descrição |
|---|---|---|
tipo | string | Sempre NFC-e |
empresaId | string | Identificador da empresa |
nfeId | string | O id enviado na emissão; relacione os callbacks por ele |
nfeStatus | string | Autorizada / Negada / Cancelada / CancelamentoNegado / ContingenciaPendente |
nfeMotivoStatus | string | null | Motivo da rejeição ou do cancelamento recusado; nos demais casos null |
nfeLinkDanfe | string | null | Link do PDF do cupom de 80 mm; null na rejeição |
nfeLinkXml | string | null | Link do XML; null na rejeição |
nfeNumero | string | null | Número da nota |
nfeSerie | string | null | Série |
nfeChaveAcesso | string | null | Chave de acesso de 44 dígitos |
nfeDataEmissao | string | null | Data de emissão, ISO-8601 UTC |
nfeDataAutorizacao | string | null | Data de autorização; em Cancelada, o momento do cancelamento |
nfeNumeroProtocolo | string | null | Protocolo de autorização; em Cancelada, o protocolo do cancelamento |
nfeQrCode | string | null | Conteúdo do QR Code; codifique a string inteira ao imprimir o cupom |
Payload de NFS-e
Mesmo registro e mesmos cabeçalhos da NF-e; veja NFS-e para o fluxo de emissão. O objeto de resultado usa tipo = NFS-e, com ordem fixa de campos.
| Evento | Disparo | nfseStatus |
|---|---|---|
nfse.authorized | Emitida com sucesso | Autorizada |
nfse.rejected | Rejeitada pela autoridade ou falha do nosso lado | Negada |
nfse.canceled | Cancelamento concluído (inclusive análise deferida) | Cancelada |
nfse.cancel_requested | Análise de cancelamento aceita | EmProcessoDeCancelamento |
nfse.cancel_denied | O cancelamento não ocorreu e o documento segue autorizado: recusa síncrona da autoridade, envio esgotado, ou pedido de análise indeferido pelo município | Autorizada |
nfse.substituted | Este documento foi substituído; a carga traz um nfseChaveSubstituta adicional | Substituida |
nfse.inbound.received | NFS-e recebida (este emitente é tomador / intermediário, exige assinatura explícita) | — |
nfse.inbound.canceled | NFS-e recebida foi cancelada (exige assinatura explícita) | — |
nfse.inbound.substituted | NFS-e recebida foi substituída (exige assinatura explícita) | — |
Nota: Semântica das assinaturas. Um aplicativo que nunca registrou nenhuma linha de assinatura recebe todos os seis eventos de saída. Assim que existir qualquer linha de assinatura para o aplicativo (por exemplo um evento de entrada ou de e-mail de notas), ele passa ao modo lista de permissão e só os eventos explicitamente habilitados são entregues. Ao assinar eventos de entrada, habilite também os eventos de saída necessários, senão as notificações de saída ficam paradas aguardando destinatário.
Autorizada:
{"tipo": "NFS-e","empresaId": "1001","nfseId": "PED-20260918-001","nfseStatus": "Autorizada","nfseMotivoStatus": null,"nfseLinkDanfse": "https://api.v2.tffiscal.com/openapi/files/danfse/…","nfseLinkXml": "https://api.v2.tffiscal.com/openapi/files/xml/9001?token=…","nfseNumero": "1287","nfseSerie": "1","nfseChaveAcesso": "3550308…","nfseIdentificacaoDps": "DPS3550308…","nfseDataEmissao": "2026-09-18T13:00:00Z","nfseDataAutorizacao": "2026-09-18T13:00:03Z","nfseNumeroProtocolo": null,"nfseAtualizadoEm": "2026-09-18T13:00:03Z"}
| Campo | Tipo | Descrição |
|---|---|---|
tipo | string | Sempre NFS-e |
empresaId | string | Identificador do emitente |
nfseId | string | O id enviado na emissão; chave de correlação de todos os eventos, presente mesmo quando a emissão falha |
nfseStatus | string | Autorizada / Negada / Cancelada / EmProcessoDeCancelamento / Substituida |
nfseMotivoStatus | string | null | Na falha, o código e a descrição da autoridade, ou o motivo da recusa da plataforma; em nfse.cancel_denied, o código e o texto do veredicto do cancelamento; null no sucesso |
nfseLinkDanfse | string | null | Link de download do PDF do DANFSe; null quando ausente |
nfseLinkXml | string | null | Link de download do XML original oficial; null quando ausente |
nfseNumero | string | null | Número da NFS-e atribuído pela autoridade; null quando a emissão não teve sucesso |
nfseSerie | string | null | Série; null quando a emissão não teve sucesso |
nfseChaveAcesso | string | null | Chave de acesso de 50 dígitos, gerada pela autoridade; null quando a emissão falha |
nfseIdentificacaoDps | string | null | Identificador do DPS com 45 caracteres; única chave de conciliação utilizável em um documento que falhou, e também null em falhas anteriores à numeração |
nfseDataEmissao | string | Data de emissão, ISO-8601 UTC |
nfseDataAutorizacao | string | null | Data de autorização; null quando não autorizada |
nfseNumeroProtocolo | string | null | Protocolo da autoridade; null quando a autoridade não devolve nenhum |
nfseAtualizadoEm | string | Momento desta mudança de estado, ISO-8601 UTC |
nfseChaveSubstituta | string | Chave de acesso de 50 dígitos do documento substituto, presente apenas em nfse.substituted |
nfseAtualizadoEm é o momento desta mudança de estado. A ordem de entrega não é garantida: retentativas e múltiplas instâncias podem fazer um evento antigo chegar depois de um novo. Compare com o horário que você já tem e descarte o mais antigo em vez de sobrescrever o estado pela ordem de chegada.
Num callback de falha o nfseChaveAcesso pode vir vazio (a autoridade nunca gerou a chave); concilie por nfseIdentificacaoDps nesse caso. Um callback de falha anterior à numeração não traz nenhum dos dois, e só o nfseId concilia.
A janela de cancelamento e o fluxo de análise estão em Cancelar NFS-e; as notas recebidas e a manifestação, em NFS-e recebidas.
Payload do veredicto de verificação
Quando a verificação de nível 2 na SEFAZ é concluída, a plataforma envia invoice.verify.completed para a sua URL de webhook. Este é o único canal de push para veredictos finais; a consulta por chave pode servir como fallback por polling.
{"version": "1.0","event_id": "1950000000000001","event_type": "invoice.verify.completed","occurred_at": "2026-07-23T17:16:23Z","data": {"chaveAcesso": "35260764962869000108550990001366171195929648","validationStatus": "VALIDATED","status": "Autorizada","cStat": "100","xMotivo": "Autorizado o uso da NF-e","protocolo": { "numero": "135262955451772", "digestValue": "oAEE...HwY=" },"dataAutorizacao": "2026-07-23T14:30:09Z","eventos": [],"verifiedAt": "2026-07-23T17:16:23Z","reason": "present only for REJECTED / VALIDATION_ERROR (stable English text)"}}
Envelope:
| Campo | Tipo | Presença | Descrição |
|---|---|---|---|
version | string | sempre | Versão do schema do payload, atualmente 1.0 |
event_id | string | sempre | Id do evento, chave de idempotência, idêntico entre retentativas |
event_type | string | sempre | invoice.verify.completed |
occurred_at | string | sempre | Momento do evento, ISO-8601 UTC |
data | object | sempre | Corpo do veredicto, abaixo |
data:
| Campo | Tipo | Presença | Descrição |
|---|---|---|---|
chaveAcesso | string | sempre | Chave de acesso de 44 dígitos da nota verificada, a chave de junção com o seu envio |
validationStatus | string | sempre | Veredicto final: VALIDATED / REJECTED / VALIDATION_ERROR, veja Verificação de NF-e |
status | string | null | anulável | Situação fiscal na SEFAZ: Autorizada / Cancelada / Denegada / Inutilizada / NaoEncontrada / Desconhecida; null em VALIDATION_ERROR (nenhum veredito da SEFAZ foi obtido) |
cStat | string | null | anulável | Código de retorno bruto da SEFAZ (por exemplo 100 autorizada, 101 cancelada); null quando a SEFAZ não foi alcançada |
xMotivo | string | null | anulável | Mensagem de retorno bruta da SEFAZ (português, sem alteração) |
protocolo | object | null | anulável | Objeto de protocolo (numero, digestValue) do registro oficial |
dataAutorizacao | string | null | anulável | Data de autorização na SEFAZ, ISO-8601 UTC |
eventos[] | array | sempre (pode ser vazio) | Eventos fiscais registrados na nota (cancelamento, cartas de correção); vazio quando não há |
verifiedAt | string | sempre | Quando a verificação de nível 2 foi concluída, ISO-8601 UTC |
reason | string | apenas em falha | Presente apenas em REJECTED / VALIDATION_ERROR; texto estável em inglês que explica o veredicto |
validationStatus | Terminal | Ação |
|---|---|---|
VALIDATED | Sim | Seguro prosseguir (liberar mercadoria, liquidar) |
REJECTED | Sim | Não prossiga; reason explica o veredicto da SEFAZ (cancelada / denegada / inutilizada / não encontrada / protocolo divergente) |
VALIDATION_ERROR | Sim, repetível | Falha de verificação do lado da plataforma, não é um julgamento da nota; como qualquer veredito, é reutilizado por 24 horas, então reenvie depois pela verificação de XML com o cabeçalho forceRevalidate: true |
Os payloads de webhook trazem apenas valores de enumeração independentes de idioma; não há campos *Description. O texto de apresentação fica a cargo do receptor.
Payload do e-mail de notas
Quando um e-mail aceito em Enviar e-mail da nota chega ao estado terminal, a plataforma envia email.sent (o serviço de envio aceitou a mensagem) ou email.failed (falha definitiva). Os dois exigem assinatura explícita: só são entregues se estiverem em eventos no registro do webhook. Depois de qualquer um deles a mesma nota aceita um novo envio. O payload é o envelope de evento, com os campos de data em snake_case.
{"version": "1.0","event_id": "1950000000000006","event_type": "email.sent","occurred_at": "2026-09-28T12:00:31Z","data": {"email_id": "2103160000000000001","id": "email-pedido-8842-1","tipo_documento": "NFe","documento_id": "pedido-8842","chave": "35241204893402000113650010000117691017244265","destinatarios": ["comprador@exemplo.com"],"status": "Enviado","motivo_falha": null}}
data:
| Campo | Tipo | Presença | Descrição |
|---|---|---|---|
email_id | string | sempre | O emailId devolvido na aceitação; use em Consultar e-mail da nota |
id | string | sempre | Identificador da requisição enviado no envio |
tipo_documento | string | sempre | NFe / NFSe / CTe / DCe |
documento_id | string | sempre | O id enviado na emissão |
chave | string | null | opcional | Chave de acesso, 44 ou 50 dígitos; null quando a falha ocorreu antes de a chave existir |
destinatarios | array | sempre | Destinatários efetivos |
status | string | sempre | Enviado (email.sent) ou Falha (email.failed) |
motivo_falha | string | null | opcional | Motivo da falha, texto livre; null em email.sent |
Exemplo de falha (NFS-e de São Paulo com espera pela chave esgotada):
{"version": "1.0","event_id": "1950000000000007","event_type": "email.failed","occurred_at": "2026-09-28T14:00:30Z","data": {"email_id": "2103160000000000002","id": "mail-sp-000045","tipo_documento": "NFSe","documento_id": "SP-20260928-045","chave": null,"destinatarios": ["financeiro@cliente.com.br"],"status": "Falha","motivo_falha": "等待超时:chaveAcesso"}}
email.sent significa que o serviço de envio aceitou a mensagem, não que ela chegou à caixa do destinatário. Modelos, anexos e estados estão em E-mail de notas.
Retentativas e circuit breaker
- Qualquer status 2xx confirma a entrega. Qualquer outro status, erro de rede ou ausência de resposta em 10 segundos conta como falha.
- Entregas com falha são reenviadas com backoff após a tentativa inicial:
1 min, 5 min, 30 min, 2 h, 6 h
- Cinco retentativas (seis tentativas no total, em cerca de 8,6 horas). Depois disso a plataforma pode reenviar a entrega manualmente mediante solicitação.
- Circuit breaker: falhas consecutivas de entrega (10 por padrão) desativam automaticamente o webhook. Enquanto estiver desativado, nenhum evento novo é enfileirado para ele. Chamar Registrar webhook novamente (ou revalidar e salvar a URL no console) restaura a entrega e zera o contador de falhas; peça à plataforma o reenvio dos eventos gerados enquanto o webhook estava desativado.
- Um novo registro passa a valer na próxima retentativa.
Requisitos do receptor
- Responda 2xx em até 10 segundos. Boa prática: persista a entrega bruta, responda 2xx imediatamente e processe de forma assíncrona.
- Verifique a origem: recalcule o HMAC sobre os bytes brutos recebidos; se você definiu um token de callback, compare também o cabeçalho
tokencom ele. - Deduplique pelo id do evento (
X-Tffiscal-Event-Idouevent_idnos payloads com envelope). - URL pública: URL absoluta
http/httpsacessível pela internet, com no máximo 512 caracteres. Endereços de loopback, privados e link-local são rejeitados no registro. - Responda à entrega de teste: salvar a URL no console envia um evento com
X-Tffiscal-Event: webhook.verify(comtoken/x-tokenquando há token definido); responda 2xx sem nenhum processamento de negócio. - Condicione as ações de negócio aos campos de resultado (
nfeStatus,cteStatus,dceStatus,nfseStatus,validationStatus), nunca apenas à resposta síncrona da API. - Nunca presuma que campos opcionais estão presentes:
dceChaveAcessopode sernull,reasonsó aparece em falha e campos vazios de NF-e chegam como valores vazios.
Solução de problemas
Callbacks não chegam?
Confirme que a URL é um endereço https/http acessível publicamente e que responde 2xx em até 10 segundos. A plataforma reenvia com backoff e desativa o webhook após falhas consecutivas; chamar o endpoint de registro novamente restaura a entrega.
A assinatura sempre falha?
A causa mais comum é desserializar o payload e serializá-lo de novo antes de calcular o HMAC, o que altera a ordem dos campos ou os espaços. Sempre calcule o hash sobre os bytes brutos recebidos e concatene o valor de X-Tffiscal-Timestamp exatamente como recebido.
O mesmo evento chegou duas vezes?
Isso é esperado na entrega ao menos uma vez. Deduplique pelo id do evento; o id de entrega varia por destino e não deve ser usado para isso.
