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ível | O que faz | Quando |
|---|---|---|
| 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ção | Devolvido 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 oficiais | Executa 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
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)
| validationStatus | Significado |
|---|---|
VALIDATED | A SEFAZ confirma que a nota é autêntica e válida (autorizada, sem cancelamento, protocolo coincide) |
REJECTED | Verificaçã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_ERROR | A 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)
- 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. - Assinatura da API: a plataforma habilita sua aplicação para
POST /openapi/v3/consultas/nf-e/xmleGET /openapi/v3/consultas/nf-e/{chave}. - 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 comevent_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
| Endpoint | Finalidade |
|---|---|
POST /openapi/v3/consultas/nf-e/xml | Envia 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çalho | Descrição |
|---|---|
X-Tffiscal-Event | invoice.verify.completed |
X-Tffiscal-Event-Id | Id do evento, a chave de idempotência: não muda entre tentativas; deduplique por ele |
X-Tffiscal-Delivery-Id | Id da entrega, único por tentativa |
X-Tffiscal-Timestamp | Segundos Unix, gerado novamente a cada tentativa |
X-Tffiscal-Signature | hex( HMAC-SHA256( secret, timestamp + "." + body ) ), minúsculas; secret = seu app_secret |
Carga
{"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:
| Campo | Tipo | Presença | Descrição |
|---|---|---|---|
| version | string | sempre | Versão do schema da carga, atualmente 1.0 |
| event_id | string | sempre | Id do evento, a chave de idempotência; idêntico entre tentativas |
| event_type | string | sempre | invoice.verify.completed |
| occurred_at | string | sempre | Hora do evento, ISO-8601 UTC |
| data | object | sempre | Corpo do veredito, abaixo |
Campos de data:
| Campo | Tipo | Presença | Descrição |
|---|---|---|---|
| chaveAcesso | string | sempre | Chave de acesso de 44 dígitos da nota verificada; chave de junção com o seu envio |
| validationStatus | string | sempre | Veredito final: VALIDATED / REJECTED / VALIDATION_ERROR (veja Ciclo de vida da verificação) |
| status | string | sempre | Situação fiscal na SEFAZ: Autorizada / Cancelada / Denegada / Inutilizada / NaoEncontrada / Desconhecida |
| cStat | string | null | anulável | Código de retorno bruto da SEFAZ (ex.: 100 = autorizada, 101 = cancelada); nulo quando a SEFAZ não foi alcançada |
| xMotivo | string | null | anulável | Mensagem de retorno bruta da SEFAZ (português, literal) |
| protocolo | object | null | anulável | Objeto Protocol (numero, digestValue), como na resposta da verificação de XML; do registro oficial |
| dataAutorizacao | string | null | anulável | Hora de autorização na SEFAZ, ISO-8601 UTC |
| eventos[] | array | sempre (pode ser vazio) | Eventos fiscais registrados contra a 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 | somente em falha | Presente 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
*Descriptionaqui. O texto de apresentação fica a cargo do receptor.
Requisitos do receptor
- 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). - Responda 2xx em até 10 segundos. Qualquer outra coisa, incluindo timeouts, conta como entrega falha.
- 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).
- Deduplique por
event_id(tentativas e fan-out para múltiplos destinos compartilham o mesmo event_id).
Tratamento do veredito
| validationStatus | Terminal? | Ação |
|---|---|---|
VALIDATED | Sim | Seguro prosseguir (liberar mercadoria, liquidar etc.) |
REJECTED | Sim | Não prosseguir; reason explica o veredito da SEFAZ (cancelada / denegada / inutilizada / não encontrada / protocolo divergente) |
VALIDATION_ERROR | Não | Falha 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: trueno 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
REJECTEDbloqueante 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:
{ "code": 10015004, "message": "Invoice not found" }
| Campo | Tipo | Descrição |
|---|---|---|
| code | integer | Código de erro da plataforma |
| message | string | Explicaçã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:
{ "success": false, "errorType": 1, "code": 10009003, "message": "Signature error" }
| Campo | Tipo | Descrição |
|---|---|---|
| success | boolean | false em todo erro |
| errorType | integer | Classe do erro: 1 erro de API, 2 rejeição da SEFAZ, 3 falha de sistema, 4 falha de validação de campos |
| code | integer | Código de erro da plataforma |
| message | string | Explicação legível, localizada |
| data | object | null | Nã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
| HTTP | code | Significado | Ação |
|---|---|---|---|
| 401 | 10009000 | Cabeçalhos de assinatura ausentes (token / sign / timestamp) | Corrija o cliente: envie os três cabeçalhos em toda requisição |
| 401 | 10009001 | Timestamp inválido ou desvio de relógio acima de ±300 s | Sincronize o relógio (NTP); gere o timestamp a cada requisição, nunca reutilize |
| 401 | 10009002 | Token inválido | Confira o app_secret; se houve rotação, atualize a configuração |
| 401 | 10009003 | Assinatura divergente | Recalcule a assinatura; veja a lista em Solução de problemas |
| 403 | 10009004 | Aplicação desativada | Contate a plataforma |
| 403 | 10009015 | Aplicação não vigente (aguardando aprovação ou rejeitada) | Aguarde a aprovação / contate a plataforma |
| 403 | 10009014 | Conta do integrador desativada | Contate a plataforma |
| 403 | 10009005 | API não assinada | Solicite a assinatura do endpoint chamado |
| 429 | 10009006 | Limite de taxa excedido | Recue 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_MALFORMEDaXML_VERSION_UNSUPPORTED(HTTP 200, dentro devalidation.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
| HTTP | Cenário | Forma do corpo |
|---|---|---|
| 200 | Requisição aceita, incluindo validação reprovada (verifique o bloco validation) | Dados simples |
| 400 | A própria requisição é inválida (vazia / grande demais / DTD / codificação / chave malformada / não encontrada) | {code, message} simples |
| 401 | Falha de autenticação (token / sign / timestamp) | Envelope da plataforma |
| 403 | Aplicação desativada / não vigente / integrador desativado / não assinada | Envelope da plataforma |
| 429 | Limite de taxa excedido | Envelope da plataforma |
| 5xx | Falha do lado da plataforma | Repita 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
- 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). POST /openapi/v3/consultas/nf-e/xmlcom um nfeProc autorizado genuíno: HTTP 200, as cinco verificações de validaçãotrue,validationStatus=PENDING_SEFAZ.- Receba o webhook
invoice.verify.completed: assinatura confere, deduplicado porevent_id, vereditoVALIDATED. - 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.
- Casos negativos: envie um XML adulterado (
SIGNATURE_INVALID); envie um XML sem protNFe (apenas aviso, ainda aceito). - Idempotência: reenvie a mesma chave e receba o veredito reutilizado; envie com
forceRevalidate: truee receba uma nova verificação com novo veredito via webhook. - Caminhos de falha: chame com
signerrado (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:
- CR/LF não removidos do corpo antes da concatenação;
- requisição GET concatenou
"null"em vez da string vazia como corpo; pathsem o prefixo/openapi, ou incluindo a query string;signenviado em maiúsculas (deve ser hex minúsculo);- o valor de
timestampusado na concatenação difere do cabeçalho (gerado de novo entre os dois); - 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.
