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)

Uma consulta por chave independente devolve os dados da nota pela chave de acesso. É um endpoint de consulta pura e não traz veredito de verificação.

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

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 pura pela chave de acesso; devolve os dados da nota e não traz veredito de verificação

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; a consulta por chave pode servir 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, único por tentativa
X-Tffiscal-TimestampSegundos Unix, gerado novamente a cada tentativa
X-Tffiscal-Signaturehex( HMAC-SHA256( secret, timestamp + "." + body ) ), minúsculas; secret = seu app_secret

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)
statusstringsempreSituação fiscal na SEFAZ: Autorizada / Cancelada / Denegada / Inutilizada / NaoEncontrada / Desconhecida
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) e depois estacionadas em uma fila de mensagens mortas (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).

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_ERRORNãoFalha de verificação do lado da plataforma, não é um juízo sobre a nota; reenvie mais tarde com forceRevalidate: true

Idempotência e nova verificação

  • A verificação de XML é idempotente pela chave: reenviar enquanto uma verificação está em andamento devolve o progresso atual; vereditos terminais são reutilizados por 24 horas (sem custo de verificação duplicado).
  • Nova verificação forçada: envie o cabeçalho forceRevalidate: true no endpoint de verificação de XML (não faz parte da assinatura). A consulta por chave tem forma GET fixa e não possui canal de nova verificação; para forçar uma nova checagem, reenvie pelo endpoint de XML.
  • Um REJECTED bloqueante de nível 1 não tem registro de nível 2; a nova verificação exige reenviar o XML.

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": "Invoice not found" }
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

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
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; 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.

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.