TF Fiscal
Documentação

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 com eventos no 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 token de callback é opcional. Quando definido (no console ou no registro), todo callback o envia sem alteração nos cabeçalhos token e x-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-Id e, nos payloads com envelope, event_id): ele é idêntico em todas as tentativas do mesmo evento.
  • Cada entrega é um HTTP POST com 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

EventoDisparado quandoPayload
invoice.authorizedNF-e autorizada pela SEFAZ (cStat 100)Objeto de resultado NF-e, nfeStatus = Autorizada
invoice.rejectedNF-e rejeitada pela SEFAZObjeto de resultado NF-e, nfeStatus = Negada, motivo em nfeMotivoStatus
invoice.canceledCancelamento da NF-e registrado na SEFAZ (cStat 135)Envelope de evento, data traz invoice_id / chave / protocolo
invoice.cce.registeredCarta de correção (CC-e) registrada em uma NF-eEnvelope 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.completedA verificação de NF-e chegou ao veredicto final (VALIDATED / REJECTED / VALIDATION_ERROR)Envelope de evento com o veredicto em data
cte.authorizedCT-e autorizado (cStat 100)Objeto de resultado CT-e, cteStatus = Autorizada
cte.rejectedCT-e rejeitado pela SEFAZ (cStat diferente de 100)Objeto de resultado CT-e, cteStatus = Negada, cteMotivoStatus = cStat - motivo
cte.canceledCancelamento do CT-e registrado (cStat 135)Objeto de resultado CT-e, cteStatus = Cancelada
cte.event.registeredQualquer 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.authorizedDC-e autorizada (cStat 100)Objeto de resultado DC-e, dceStatus = Autorizada
dce.rejectedDC-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.canceledCancelamento da DC-e registrado (cStat 135 / 136 / 155)Objeto de resultado DC-e, dceStatus = Cancelada
dce.cancel_rejectedA SEFAZ rejeitou o cancelamento da DC-e, ou a tarefa de cancelamento falhou de forma terminalObjeto de resultado DC-e, dceStatus = CancelamentoNegado, o documento continua autorizado
nfce.authorizedNFC-e autorizada; também enviado quando uma nota offline é autorizada após a transmissãoObjeto de resultado NFC-e, nfeStatus = Autorizada
nfce.rejectedNFC-e rejeitada pela SEFAZ, ou falhou no processamento da plataforma; também enviado quando uma nota offline é rejeitada na transmissãoObjeto de resultado NFC-e, nfeStatus = Negada
nfce.canceledCancelamento da NFC-e (inclusive por substituição) registradoObjeto de resultado NFC-e, nfeStatus = Cancelada
nfce.cancel_rejectedA SEFAZ rejeitou o cancelamento da NFC-eObjeto de resultado NFC-e, nfeStatus = CancelamentoNegado, a nota continua válida
nfce.contingency_issuedNFC-e emitida offline com a SEFAZ indisponívelObjeto de resultado NFC-e, nfeStatus = ContingenciaPendente
nfse.authorizedNFS-e emitida com sucessoObjeto de resultado NFS-e, nfseStatus = Autorizada
nfse.rejectedNFS-e rejeitada pela autoridade, ou falha do lado da plataformaObjeto de resultado NFS-e, nfseStatus = Negada, motivo em nfseMotivoStatus
nfse.canceledCancelamento da NFS-e concluído (inclusive análise deferida)Objeto de resultado NFS-e, nfseStatus = Cancelada
nfse.cancel_requestedAnálise de cancelamento da NFS-e aceitaObjeto de resultado NFS-e, nfseStatus = EmProcessoDeCancelamento
nfse.cancel_deniedO cancelamento da NFS-e não ocorreu e o documento continua autorizadoObjeto de resultado NFS-e, nfseStatus = Autorizada, motivo em nfseMotivoStatus
nfse.substitutedNFS-e substituída por outro documentoObjeto de resultado NFS-e, nfseStatus = Substituida, mais nfseChaveSubstituta
nfse.inbound.receivedNFS-e recebida, sendo este emitente tomador / intermediário (exige assinatura explícita)Notificação de NFS-e recebida
nfse.inbound.canceledNFS-e recebida foi cancelada (exige assinatura explícita)Notificação de NFS-e recebida
nfse.inbound.substitutedNFS-e recebida foi substituída (exige assinatura explícita)Notificação de NFS-e recebida
nfse.inbound.manifestedA 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_failedA 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.sentE-mail da nota entregue ao serviço de envio (exige assinatura explícita)Envelope de evento, data.status = Enviado
email.failedE-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 campo event_id; deduplique pelo cabeçalho X-Tffiscal-Event-Id.
  • Envelope de evento: { "version", "event_id", "event_type", "occurred_at", "data" }. Usado por invoice.canceled, invoice.cce.registered, invoice.verify.completed, cte.event.registered, nfse.inbound.* e email.sent / email.failed. Os campos dentro de data só são adicionados, nunca renomeados ou removidos; uma mudança incompatível incrementa version.

Nota: rejeições da SEFAZ nunca são erros HTTP da chamada de emissão. Elas aparecem como status Negada na consulta e como evento invoice.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çalhoSignificado
X-Tffiscal-EventCódigo do evento, por exemplo invoice.authorized (webhook.verify na entrega de teste enviada ao salvar uma URL no console)
X-Tffiscal-Event-IdId do evento, a chave de idempotência; não muda entre retentativas e é compartilhado por todos os destinos do mesmo evento
X-Tffiscal-Delivery-IdIdentificador da entrega; não deduplique por ele, use o id do evento
X-Tffiscal-TimestampSegundos Unix, gerado novamente a cada tentativa
X-Tffiscal-Signaturehex( HMAC-SHA256( app_secret, timestamp + "." + body ) ), em minúsculas
tokenToken de callback opcional: o valor definido no console ou enviado em Registrar webhook, devolvido sem alteração. Ausente quando nenhum token está definido
x-tokenMesmo 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:

javascript
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:

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:

json
{
"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:

json
{
"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"
}
CampoTipoDescrição
tipostringSempre NF-e
empresaIdstringIdentificador da empresa
nfeIdstringO id enviado na emissão
nfeStatusstringAutorizada / Negada
nfeMotivoStatusstringMotivo da negativa: código de status da SEFAZ + descrição; vazio quando autorizada
nfeLinkDanfestringLink de download do PDF do DANFE (veja Download de arquivos; utilizável no momento do callback); vazio quando negada
nfeLinkXmlstringLink de download do XML autorizado; vazio quando negada
nfeNumerostringNúmero da nota
nfeSeriestringSérie
nfeChaveAcessostringChave de acesso de 44 dígitos
nfeDataEmissaostringData de emissão, ISO-8601 UTC
nfeDataAutorizacaostringData de autorização; vazio quando negada
nfeNumeroProtocolostringNúmero do protocolo de autorização; vazio quando negada
nfeDigestValuestringDigest 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.

EventoDisparocteStatus
cte.authorizedAutorização 100Autorizada
cte.rejectedRejeiçã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.canceledCancelamento 135Cancelada
cte.event.registeredQualquer outro evento registradoEnvelope de evento, data traz event_code / n_seq / protocolo

Autorizado:

json
{
"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": "..."
}
CampoTipoDescrição
tipostringSempre CT-e
empresaIdstringIdentificador da empresa
cteIdstringO id enviado na emissão
cteStatusstringAutorizada / Negada / Cancelada
cteMotivoStatusstring | nullcStat - motivo quando rejeitado; null nos demais casos
cteLinkDactestring | nullLink de download do PDF do DACTE; null quando rejeitado
cteLinkXmlstring | nullLink de download do XML autorizado; null quando rejeitado
cteNumerostringNúmero do documento
cteSeriestringSérie
cteChaveAcessostringChave de acesso de 44 dígitos
cteDataEmissaostringData de emissão, ISO-8601 UTC
cteDataAutorizacaostring | nullData de autorização; null quando rejeitado
cteNumeroProtocolostring | nullNúmero do protocolo de autorização; null quando rejeitado
cteDigestValuestring | nullDigest 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):

json
{
"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"
}
}
CampoTipoDescrição
event_codestringCódigo do evento na SEFAZ, por exemplo 110110 carta de correção, 110180 comprovante de entrega
n_seqstringNúmero de sequência do evento
protocolostringNú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.

EventoDisparodceStatus
dce.authorizedAutorização 100Autorizada
dce.rejectedRejeiçã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.canceledCancelamento registrado (135 / 136 / 155)Cancelada (dceDataAutorizacao é o momento do registro do cancelamento, dceNumeroProtocolo o protocolo do evento de cancelamento)
dce.cancel_rejectedA SEFAZ rejeitou o cancelamento ou a tarefa de cancelamento falhou de forma terminalCancelamentoNegado (o documento continua autorizado; traz o protocolo de autorização / digest / link do XML)

Autorizada:

json
{
"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:

json
{
"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:

json
{
"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:

json
{
"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:

json
{
"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="
}
CampoTipoDescrição
tipostringSempre DC-e
empresaIdstringIdentificador da empresa
dceIdstringO id enviado na emissão; chave de correlação de todos os eventos, presente mesmo quando o documento nunca foi numerado
dceStatusstringAutorizada / Negada / Cancelada / CancelamentoNegado
dceMotivoStatusstring | nullcStat - motivo em rejeição da SEFAZ; apenas o motivo em falha terminal sem cStat; null em sucesso
dceLinkDacestring | nullLink de download do PDF do DACE; null quando rejeitada e em CancelamentoNegado
dceLinkXmlstring | nullLink de download do XML autorizado; null quando rejeitada
dceNumerostring | nullNúmero do documento; null quando nunca numerado
dceSeriestring | nullSérie; null quando nunca numerado
dceChaveAcessostring | nullChave de acesso de 44 dígitos; null quando nunca numerado
dceDataEmissaostringData de emissão, ISO-8601 UTC (momento da aceitação em falha antes da numeração)
dceDataAutorizacaostring | nullData de autorização; momento do registro do cancelamento em Cancelada; null quando rejeitada
dceNumeroProtocolostring | nullProtocolo de autorização; protocolo do evento de cancelamento em Cancelada; null quando rejeitada
dceDigestValuestring | nullDigest 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 eventoGatilhonfeStatus
nfce.authorizedAutorizada, ou autorizada após a transmissão de uma nota offlineAutorizada
nfce.rejectedRejeição da SEFAZ, falha no processamento da plataforma, ou rejeição de nota offline transmitidaNegada (nfeMotivoStatus traz código e motivo)
nfce.canceledCancelamento ou cancelamento por substituição registradoCancelada (nfeDataAutorizacao é o momento do cancelamento, nfeNumeroProtocolo o protocolo do cancelamento)
nfce.cancel_rejectedA SEFAZ rejeitou o cancelamentoCancelamentoNegado (a nota continua válida; motivo em nfeMotivoStatus)
nfce.contingency_issuedEmitida offline com a SEFAZ indisponívelContingenciaPendente (link do cupom e nfeQrCode, sem protocolo)

Autorizada:

json
{
"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:

json
{
"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
}
CampoTipoDescrição
tipostringSempre NFC-e
empresaIdstringIdentificador da empresa
nfeIdstringO id enviado na emissão; relacione os callbacks por ele
nfeStatusstringAutorizada / Negada / Cancelada / CancelamentoNegado / ContingenciaPendente
nfeMotivoStatusstring | nullMotivo da rejeição ou do cancelamento recusado; nos demais casos null
nfeLinkDanfestring | nullLink do PDF do cupom de 80 mm; null na rejeição
nfeLinkXmlstring | nullLink do XML; null na rejeição
nfeNumerostring | nullNúmero da nota
nfeSeriestring | nullSérie
nfeChaveAcessostring | nullChave de acesso de 44 dígitos
nfeDataEmissaostring | nullData de emissão, ISO-8601 UTC
nfeDataAutorizacaostring | nullData de autorização; em Cancelada, o momento do cancelamento
nfeNumeroProtocolostring | nullProtocolo de autorização; em Cancelada, o protocolo do cancelamento
nfeQrCodestring | nullConteú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.

EventoDisparonfseStatus
nfse.authorizedEmitida com sucessoAutorizada
nfse.rejectedRejeitada pela autoridade ou falha do nosso ladoNegada
nfse.canceledCancelamento concluído (inclusive análise deferida)Cancelada
nfse.cancel_requestedAnálise de cancelamento aceitaEmProcessoDeCancelamento
nfse.cancel_deniedO cancelamento não ocorreu e o documento segue autorizado: recusa síncrona da autoridade, envio esgotado, ou pedido de análise indeferido pelo municípioAutorizada
nfse.substitutedEste documento foi substituído; a carga traz um nfseChaveSubstituta adicionalSubstituida
nfse.inbound.receivedNFS-e recebida (este emitente é tomador / intermediário, exige assinatura explícita)—
nfse.inbound.canceledNFS-e recebida foi cancelada (exige assinatura explícita)—
nfse.inbound.substitutedNFS-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:

json
{
"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"
}
CampoTipoDescrição
tipostringSempre NFS-e
empresaIdstringIdentificador do emitente
nfseIdstringO id enviado na emissão; chave de correlação de todos os eventos, presente mesmo quando a emissão falha
nfseStatusstringAutorizada / Negada / Cancelada / EmProcessoDeCancelamento / Substituida
nfseMotivoStatusstring | nullNa 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
nfseLinkDanfsestring | nullLink de download do PDF do DANFSe; null quando ausente
nfseLinkXmlstring | nullLink de download do XML original oficial; null quando ausente
nfseNumerostring | nullNúmero da NFS-e atribuído pela autoridade; null quando a emissão não teve sucesso
nfseSeriestring | nullSérie; null quando a emissão não teve sucesso
nfseChaveAcessostring | nullChave de acesso de 50 dígitos, gerada pela autoridade; null quando a emissão falha
nfseIdentificacaoDpsstring | nullIdentificador 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
nfseDataEmissaostringData de emissão, ISO-8601 UTC
nfseDataAutorizacaostring | nullData de autorização; null quando não autorizada
nfseNumeroProtocolostring | nullProtocolo da autoridade; null quando a autoridade não devolve nenhum
nfseAtualizadoEmstringMomento desta mudança de estado, ISO-8601 UTC
nfseChaveSubstitutastringChave 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.

json
{
"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:

CampoTipoPresençaDescrição
versionstringsempreVersão do schema do payload, atualmente 1.0
event_idstringsempreId do evento, chave de idempotência, idêntico entre retentativas
event_typestringsempreinvoice.verify.completed
occurred_atstringsempreMomento do evento, ISO-8601 UTC
dataobjectsempreCorpo do veredicto, abaixo

data:

CampoTipoPresençaDescrição
chaveAcessostringsempreChave de acesso de 44 dígitos da nota verificada, a chave de junção com o seu envio
validationStatusstringsempreVeredicto final: VALIDATED / REJECTED / VALIDATION_ERROR, veja Verificação de NF-e
statusstring | nullanulávelSituação fiscal na SEFAZ: Autorizada / Cancelada / Denegada / Inutilizada / NaoEncontrada / Desconhecida; null em VALIDATION_ERROR (nenhum veredito da SEFAZ foi obtido)
cStatstring | nullanulávelCódigo de retorno bruto da SEFAZ (por exemplo 100 autorizada, 101 cancelada); null quando a SEFAZ não foi alcançada
xMotivostring | nullanulávelMensagem de retorno bruta da SEFAZ (português, sem alteração)
protocoloobject | nullanulávelObjeto de protocolo (numero, digestValue) do registro oficial
dataAutorizacaostring | nullanulávelData de autorização na SEFAZ, ISO-8601 UTC
eventos[]arraysempre (pode ser vazio)Eventos fiscais registrados na nota (cancelamento, cartas de correção); vazio quando não há
verifiedAtstringsempreQuando a verificação de nível 2 foi concluída, ISO-8601 UTC
reasonstringapenas em falhaPresente apenas em REJECTED / VALIDATION_ERROR; texto estável em inglês que explica o veredicto
validationStatusTerminalAção
VALIDATEDSimSeguro prosseguir (liberar mercadoria, liquidar)
REJECTEDSimNão prossiga; reason explica o veredicto da SEFAZ (cancelada / denegada / inutilizada / não encontrada / protocolo divergente)
VALIDATION_ERRORSim, repetívelFalha 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.

json
{
"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:

CampoTipoPresençaDescrição
email_idstringsempreO emailId devolvido na aceitação; use em Consultar e-mail da nota
idstringsempreIdentificador da requisição enviado no envio
tipo_documentostringsempreNFe / NFSe / CTe / DCe
documento_idstringsempreO id enviado na emissão
chavestring | nullopcionalChave de acesso, 44 ou 50 dígitos; null quando a falha ocorreu antes de a chave existir
destinatariosarraysempreDestinatários efetivos
statusstringsempreEnviado (email.sent) ou Falha (email.failed)
motivo_falhastring | nullopcionalMotivo da falha, texto livre; null em email.sent

Exemplo de falha (NFS-e de São Paulo com espera pela chave esgotada):

json
{
"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:
text
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

  1. Responda 2xx em até 10 segundos. Boa prática: persista a entrega bruta, responda 2xx imediatamente e processe de forma assíncrona.
  2. Verifique a origem: recalcule o HMAC sobre os bytes brutos recebidos; se você definiu um token de callback, compare também o cabeçalho token com ele.
  3. Deduplique pelo id do evento (X-Tffiscal-Event-Id ou event_id nos payloads com envelope).
  4. URL pública: URL absoluta http / https acessível pela internet, com no máximo 512 caracteres. Endereços de loopback, privados e link-local são rejeitados no registro.
  5. Responda à entrega de teste: salvar a URL no console envia um evento com X-Tffiscal-Event: webhook.verify (com token / x-token quando há token definido); responda 2xx sem nenhum processamento de negócio.
  6. Condicione as ações de negócio aos campos de resultado (nfeStatus, cteStatus, dceStatus, nfseStatus, validationStatus), nunca apenas à resposta síncrona da API.
  7. Nunca presuma que campos opcionais estão presentes: dceChaveAcesso pode ser null, reason só 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.