TF Fiscal
Documentação

Verificação de NF-e

Verificação em dois níveis de NF-e de terceiros: verificação de XML, consulta por chave, veredito final entregue por webhook, idempotência e modelo de erros.

Visão geral

A plataforma executa uma verificação em dois níveis de NF-e de terceiros (modelo 55):

NívelO que fazQuando
Nível 1 (síncrono)Validação local do XML: sintaxe, layout XSD da NF-e 4.00, assinatura digital e titularidade do certificado, estrutura da chave de acesso de 44 dígitos e coerência com os campos do documento, integridade do protocolo de autorizaçãoDevolvido imediatamente na resposta da API
Nível 2 (assíncrono)Verificação de autenticidade em tempo real na SEFAZ (a autoridade fiscal estadual): situação da autorização, eventos de cancelamento, número do protocolo e digest comparados com os registros oficiaisExecuta depois que o nível 1 é aprovado; o veredito é entregue por webhook (veja Recebendo o veredito final), e a consulta por chave devolve o estado atual como alternativa de polling

Uma consulta por chave independente devolve os dados da nota pela chave de acesso. Para uma chave que você enviou pela verificação de XML, ela também devolve o bloco validation atual e serve como alternativa de polling quando uma entrega de webhook se perde.

Os dois endpoints exigem os três cabeçalhos de assinatura, veja Autenticação. As respostas são objetos simples (sem envelope da plataforma); erros de requisição são um objeto simples {code, message}.

Ciclo de vida da verificação

text
envio do XML ──► Nível 1
├─ erro bloqueante ────────────► REJECTED (terminal)
└─ aprovado ──► PENDING_SEFAZ ──► VALIDATING (Nível 2)
├──► VALIDATED (terminal)
├──► REJECTED (terminal, com reason)
└──► VALIDATION_ERROR (repetível, com reason)
validationStatusSignificado
VALIDATEDA SEFAZ confirma que a nota é autêntica e válida (autorizada, sem cancelamento, protocolo coincide)
REJECTEDVerificação reprovada: erro bloqueante de nível 1, ou a SEFAZ informa que a nota está cancelada / denegada / inutilizada / não encontrada / com protocolo divergente. reason é informado
VALIDATION_ERRORA verificação não pôde ser concluída (limitação da SEFAZ ou falha de consulta após as tentativas). Não é um juízo sobre a nota em si; reenvie mais tarde com forceRevalidate: true

Pré-requisitos (uma única vez)

  1. Credencial da aplicação: solicite uma aplicação e receba o app_secret. Ele é exibido apenas uma vez; guarde-o com segurança. Em caso de vazamento, solicite a rotação.
  2. Assinatura da API: a plataforma habilita sua aplicação para POST /openapi/v3/consultas/nf-e/xml e GET /openapi/v3/consultas/nf-e/{chave}.
  3. Endpoint de webhook: registre a URL de callback em Registrar webhook e assine o evento invoice.verify.completed (o único canal de envio dos vereditos de nível 2). Ao salvar a URL, a plataforma envia imediatamente uma entrega de teste com event_type=webhook.verify; seu endpoint deve responder 2xx para que o salvamento tenha êxito (responder 200 sem processar é suficiente para o evento de teste).

Endpoints

EndpointFinalidade
POST /openapi/v3/consultas/nf-e/xmlEnvia o XML bruto da NF-e; devolve o veredito de nível 1 e a nota interpretada, inicia o nível 2
GET /openapi/v3/consultas/nf-e/{chave}Consulta pela chave de acesso; devolve os dados da nota e, para chaves que você enviou, o bloco validation atual

Recebendo o veredito final

Quando a verificação de nível 2 na SEFAZ é concluída, a plataforma faz um POST para a URL de webhook registrada. Este é o único canal de envio dos vereditos finais; se uma entrega se perder, a consulta por chave devolve o validationStatus atual e serve como alternativa de polling.

Cabeçalhos da requisição

CabeçalhoDescrição
X-Tffiscal-Eventinvoice.verify.completed
X-Tffiscal-Event-IdId do evento, a chave de idempotência: não muda entre tentativas; deduplique por ele
X-Tffiscal-Delivery-IdId da entrega, um por URL de destino; não muda entre as tentativas para esse destino (deduplique pelo id do evento, não por ele)
X-Tffiscal-TimestampSegundos Unix, gerado novamente a cada tentativa
X-Tffiscal-Signaturehex( HMAC-SHA256( secret, timestamp + "." + body ) ), minúsculas; secret = seu app_secret para uma URL de webhook no nível da aplicação (uma URL de reserva no nível do tenant é assinada com o secret configurado para ela)

Carga

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": "oAEEuC3tGmb2W7ZJxWkNVWuBHwY=" },
"dataAutorizacao": "2026-07-23T14:30:09Z",
"eventos": [],
"verifiedAt": "2026-07-23T17:16:23Z"
}
}

Campos do envelope:

CampoTipoPresençaDescrição
versionstringsempreVersão do schema da carga, atualmente 1.0
event_idstringsempreId do evento, a chave de idempotência; idêntico entre tentativas
event_typestringsempreinvoice.verify.completed
occurred_atstringsempreHora do evento, ISO-8601 UTC
dataobjectsempreCorpo do veredito, abaixo

Campos de data:

CampoTipoPresençaDescrição
chaveAcessostringsempreChave de acesso de 44 dígitos da nota verificada; chave de junção com o seu envio
validationStatusstringsempreVeredito final: VALIDATED / REJECTED / VALIDATION_ERROR (veja Ciclo de vida da verificação)
statusstring | nullanulávelSituação fiscal na SEFAZ: Autorizada / Cancelada / Denegada / Inutilizada / NaoEncontrada / Desconhecida; nulo em VALIDATION_ERROR (nenhum veredito da SEFAZ foi obtido)
cStatstring | nullanulávelCódigo de retorno bruto da SEFAZ (ex.: 100 = autorizada, 101 = cancelada); nulo quando a SEFAZ não foi alcançada
xMotivostring | nullanulávelMensagem de retorno bruta da SEFAZ (português, literal)
protocoloobject | nullanulávelObjeto Protocol (numero, digestValue), como na resposta da verificação de XML; do registro oficial
dataAutorizacaostring | nullanulávelHora de autorização na SEFAZ, ISO-8601 UTC
eventos[]arraysempre (pode ser vazio)Eventos fiscais registrados contra a 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
reasonstringsomente em falhaPresente somente para REJECTED / VALIDATION_ERROR; texto estável em inglês que explica o veredito

Nota: as cargas de webhook trazem apenas valores enumerados independentes de idioma; não há campos *Description aqui. O texto de apresentação fica a cargo do receptor.

Requisitos do receptor

  1. Verifique a assinatura: recalcule HMAC-SHA256(secret, timestamp + "." + rawBody) e compare com o cabeçalho. Use os bytes brutos recebidos; não desserialize e serialize de novo antes (a reordenação de campos quebra a assinatura).
  2. Responda 2xx em até 10 segundos. Qualquer outra coisa, incluindo timeouts, conta como entrega falha.
  3. Entregas falhas são repetidas com backoff 1m / 5m / 30m / 2h / 6h (5 tentativas); reenvio manual disponível sob solicitação.
  4. Deduplique por event_id (tentativas e fan-out para múltiplos destinos compartilham o mesmo event_id).
  5. Uma mesma chave pode gerar vários eventos ao longo do tempo: uma nova verificação com forceRevalidate: true, um reenvio após a janela de 24 horas ou uma rechecagem em segundo plano disparada pela consulta por chave terminam, cada um, em um novo evento com seu próprio event_id. Fique com o veredito do evento com o occurred_at mais recente.

Tratamento do veredito

validationStatusTerminal?Ação
VALIDATEDSimSeguro prosseguir (liberar mercadoria, liquidar etc.)
REJECTEDSimNão prosseguir; reason explica o veredito da SEFAZ (cancelada / denegada / inutilizada / não encontrada / protocolo divergente)
VALIDATION_ERRORSim, repetívelFalha de verificação do lado da plataforma, não é um juízo sobre a nota. Como qualquer veredito, é reutilizado por 24 horas; reenvie mais tarde com forceRevalidate: true

Idempotência e nova verificação

  • A verificação de XML é idempotente pela chave (a chave de acesso dentro do XML enviado):
    • Enquanto o nível 2 está em andamento (PENDING_SEFAZ / VALIDATING), reenviar devolve o progresso atual e não inicia outra checagem. forceRevalidate: true não reinicia nem acelera uma checagem já em curso.
    • Um veredito terminal (VALIDATED / REJECTED / VALIDATION_ERROR) é reutilizado por 24 horas a partir do momento em que foi obtido: reenviar dentro dessa janela o devolve sem consultar a SEFAZ de novo. Depois de 24 horas, um reenvio simples executa a verificação completa (nível 1 + nível 2) outra vez.
    • Uma resposta reutilizada reflete o XML enviado originalmente (dados da nota, checagens de nível 1, erros e avisos) junto com o validationStatus atual. Se você reenviar um XML corrigido para a mesma chave dentro da janela, envie forceRevalidate: true; caso contrário, recebe o resultado anterior.
  • Nova verificação forçada: envie o cabeçalho forceRevalidate: true no endpoint de verificação de XML (não faz parte da assinatura). Ele ignora a reutilização e executa o nível 1 e o nível 2 de novo; o novo veredito chega como um novo evento de webhook.
  • Consulta por chave: não força uma rechecagem. Ela agenda uma por conta própria somente quando a última resposta da SEFAZ tem mais de 24 horas (veja consulta por chave).
  • Um REJECTED bloqueante de nível 1 não tem registro de nível 2; para verificar de novo, reenvie o XML (com forceRevalidate: true dentro da janela de 24 horas).
  • As verificações pertencem à sua conta de integrador: a reutilização, a consulta por chave e os webhooks só enxergam os envios da sua conta. Outro integrador verificando a mesma nota não afeta a sua; cada conta é verificada de forma independente e recebe o próprio veredito.

Modelo de erros

Duas formas de erro

Erros de negócio e de requisição (HTTP 400) são devolvidos como objeto simples:

json
{ "code": 10015004, "message": "Nota fiscal não encontrada: 35260764962869000108550990001366171195929648" }
CampoTipoDescrição
codeintegerCódigo de erro da plataforma
messagestringExplicação legível, localizada (veja Localização das respostas)

Erros da camada de autenticação (HTTP 401 / 403 / 429) são produzidos pelo gateway da plataforma antes de a requisição chegar à API e usam o envelope da plataforma:

json
{ "success": false, "errorType": 1, "code": 10009003, "message": "Signature error" }
CampoTipoDescrição
successbooleanfalse em todo erro
errorTypeintegerClasse do erro: 1 erro de API, 2 rejeição da SEFAZ, 3 falha de sistema, 4 falha de validação de campos
codeintegerCódigo de erro da plataforma
messagestringExplicação legível, localizada
dataobject | nullNão preenchido em erros

Seu tratamento de erros precisa aceitar as duas formas. Uma regra robusta: interprete o corpo como JSON, leia code de qualquer uma das formas e trate success=false ou HTTP >= 400 como falha.

Erros de autenticação e autorização

HTTPcodeSignificadoAção
40110009000Cabeçalhos de assinatura ausentes (token / sign / timestamp)Corrija o cliente: envie os três cabeçalhos em toda requisição
40110009001Timestamp inválido ou desvio de relógio acima de ±300 sSincronize o relógio (NTP); gere o timestamp a cada requisição, nunca reutilize
40110009002Token inválidoConfira o app_secret; se houve rotação, atualize a configuração
40110009003Assinatura divergenteRecalcule a assinatura; veja a lista em Solução de problemas
40310009004Aplicação desativadaContate a plataforma
40310009015Aplicação não vigente (aguardando aprovação ou rejeitada)Aguarde a aprovação / contate a plataforma
40310009014Conta do integrador desativadaContate a plataforma
40310009005API não assinadaSolicite a assinatura do endpoint chamado
42910009006Limite de taxa excedidoRecue e repita; suavize a taxa de chamadas. Os limites são aplicados por aplicação e por grupo de endpoints
41310009027Corpo da requisição acima do limite de tamanho do gatewayA verificação de XML aceita no máximo 1 MB; envie uma única NF-e por requisição

Orientação de repetição: 401 e 403 são erros de configuração; repetir sem corrigir é inútil e pode disparar o limite de taxa. 429 pode ser repetido com backoff exponencial (inicie em 1 s, dobre até 30 s, adicione jitter).

Erros de requisição e de validação

Documentados em cada página de endpoint:

  • Verificar XML: erros de requisição 10015000 a 10015003 (HTTP 400, forma simples, a requisição nunca entra na validação) e o catálogo de validação de nível 1 de XML_MALFORMED a XML_VERSION_UNSUPPORTED (HTTP 200, dentro de validation.errors[]).
  • Consultar por chave: 10015104 (chave malformada) e 10015004 (nota não encontrada), HTTP 400, forma simples.
  • Os resultados de nível 2 não são erros HTTP; veja Tratamento do veredito.

Referência rápida de status HTTP

HTTPCenárioForma do corpo
200Requisição aceita, incluindo validação reprovada (verifique o bloco validation)Dados simples
400A própria requisição é inválida (vazia / grande demais / DTD / codificação / chave malformada / não encontrada){code, message} simples
401Falha de autenticação (token / sign / timestamp)Envelope da plataforma
413Corpo acima do limite de tamanho do gatewayEnvelope da plataforma
403Aplicação desativada / não vigente / integrador desativado / não assinadaEnvelope da plataforma
429Limite de taxa excedidoEnvelope da plataforma
5xxFalha do lado da plataformaRepita com backoff; se persistir, contate a plataforma com o timestamp e o caminho da requisição que falhou

Localização das respostas

Os campos message e *Description seguem o idioma da requisição: o cabeçalho Language (en / pt / es / zh) tem precedência, depois Accept-Language (aceita pt-BR e valores q). Sem cabeçalho de idioma, as respostas usam português (pt) por padrão.

Para decisões programáticas use sempre os campos enumerados independentes de idioma (validationStatus, status, errors[].code); nunca compare textos descritivos.

Checklist de integração

  1. Obtenha o app_secret; confira sua implementação de assinatura com a saída do assistente de assinatura (uma vez para POST com corpo XML, uma vez para GET com corpo vazio).
  2. POST /openapi/v3/consultas/nf-e/xml com um nfeProc autorizado genuíno: HTTP 200, as cinco verificações de validação true, validationStatus=PENDING_SEFAZ.
  3. Receba o webhook invoice.verify.completed: assinatura confere, deduplicado por event_id, veredito VALIDATED.
  4. Consulta por chave: uma chave já enviada devolve 200 com os dados da nota e o bloco validation (mesmo validationStatus do veredito do webhook); uma chave inexistente devolve 400 + 10015004; uma chave com dígito verificador errado devolve 400 + 10015104.
  5. Casos negativos: envie um XML adulterado (SIGNATURE_INVALID); envie um XML sem protNFe (apenas aviso, ainda aceito).
  6. Idempotência: reenvie a mesma chave e receba o veredito reutilizado; envie com forceRevalidate: true e receba uma nova verificação com novo veredito via webhook.
  7. Caminhos de falha: chame com sign errado (401, código 10009003); chame um endpoint não assinado (403, código 10009005).

Solução de problemas

A assinatura nunca confere (401, código 10009003)?

Verifique, por ordem de frequência:

  1. CR/LF não removidos do corpo antes da concatenação;
  2. requisição GET concatenou "null" em vez da string vazia como corpo;
  3. path sem o prefixo /openapi, ou incluindo a query string;
  4. sign enviado em maiúsculas (deve ser hex minúsculo);
  5. o valor de timestamp usado na concatenação difere do cabeçalho (gerado de novo entre os dois);
  6. bytes do corpo recodificados (é preciso fazer o hash exatamente dos bytes enviados na rede, UTF-8).

HTTP 200, mas a nota é falsa?

O nível 1 só avalia se o XML é internamente coerente. A autenticidade é decidida pelo nível 2 na SEFAZ e entregue por webhook; condicione sua ação de negócio (liberação, liquidação) ao VALIDATED do webhook, nunca apenas à resposta síncrona.

O status da resposta síncrona é a situação na SEFAZ?

Não. Na primeira resposta, o status é lido do protocolo dentro do XML que você enviou; a SEFAZ ainda não foi consultada. A situação confirmada pela SEFAZ chega com o veredito de nível 2: o status do webhook, ou a consulta por chave depois que o nível 2 obtém resposta.

REJECTED com status NaoEncontrada: a nota é falsa?

Não necessariamente. A SEFAZ respondeu "não encontrada" (cStat 217). A plataforma não conclui com o primeiro 217: consulta a SEFAZ de novo várias vezes ao longo de alguns minutos, porque uma nota recém-autorizada pode levar um tempo para ficar consultável, e só um 217 persistente vira REJECTED com NaoEncontrada. Causas comuns:

  1. a nota foi autorizada há instantes e a SEFAZ ainda não terminou de propagá-la; verifique de novo mais tarde com forceRevalidate: true;
  2. a nota foi emitida no ambiente de homologação da SEFAZ (ambienteEmissao=Homologacao): o nível 2 consulta o ambiente de produção, onde notas de teste não existem;
  3. a nota nunca foi autorizada com essa chave.

Isso é diferente do 10015004 da consulta por chave, que significa que nenhuma fonte de dados da plataforma conhece a chave.

A assinatura do webhook continua falhando?

A causa mais comum é desserializar a carga e serializá-la de novo antes de calcular o HMAC, o que altera a ordem dos campos ou os espaços em branco. Sempre faça o hash dos bytes brutos recebidos.

VALIDATION_ERROR é um problema da nota?

Não. Significa que o canal de verificação da plataforma falhou (ex.: limitação da SEFAZ); a nota em si não foi julgada. Reenvie mais tarde com forceRevalidate: true.

Desvio de relógio (401, código 10009001)?

O relógio do seu servidor difere do nosso em mais de 300 segundos. Use NTP. Nunca armazene nem reutilize timestamps entre requisições.