Distribuição de NF-e
Visão geral
O workflow nfe-distribution consulta documentos fiscais eletrônicos de interesse de cada cliente habilitado. Ele não acessa a SEFAZ diretamente: envia o ultNSU atual ao proxy NF-e da Toterus, interpreta a resposta retDistDFeInt e persiste tentativas, documentos e o novo cursor no NFE_DB.
A execução pertence a apps/workflows, usa a implementação de packages/nfe-distribution-workflow e roda pelo schedule 0 7 * * *. Também pode ser disparada manualmente pelo monitor. O parâmetro maxCallsPerClient limita cada drenagem entre 1 e 500 chamadas, com padrão 100.
Fluxo de execução
flowchart TD; Trigger{Schedule nativo ou ação manual}; Trigger --> Instance[Instância Cloudflare Workflow]; Instance --> Run[runInstrumented: nfe-distribution]; Run --> Load[Carregar clientes elegíveis]; Load --> Lease[Adquirir lease por cliente]; Lease --> Request[Proxy Toterus: consultar com ultNSU]; Request --> Parse[Interpretar retDistDFeInt]; Parse --> Decision{Classificar resposta}; Decision -->|138 ou 137 válidos| Persist[Persistir tentativa e documentos]; Persist --> NfeDb[(NFE_DB)]; Persist --> Cursor[Avançar current_ult_nsu]; Cursor --> More{Há mais NSUs e limite disponível?}; More -->|Sim| Request; More -->|Não| Release[Liberar lease]; Decision -->|656| Block[Registrar bloqueio sem avançar cursor]; Decision -->|Falha ou anomalia| Failure[Registrar falha sem avançar cursor]; Block --> NfeDb; Failure --> NfeDb; Block --> Release; Failure --> Release; Run -. eventos .-> Telemetry[(DB: workflow runs e eventos)]; Telemetry --> Monitor[Monitor]; Release --> Result[Concluir cliente e run];
- Carrega clientes habilitados, não bloqueados e disponíveis para execução.
- Sinaliza clientes sem resposta há 70 dias como risco de retenção.
- Adquire um lease de 15 minutos por cliente para impedir drenagens concorrentes.
- Lê
current_ult_nsue solicita uma página ao endpoint/nfe/distribuicaodo proxy. - Interpreta
cStat,xMotivo,ultNSU,maxNSUe até 50 elementosdocZip. - Persiste a tentativa e os documentos antes de atualizar o cursor.
- Continua enquanto a resposta permitir avanço, ainda houver NSUs pendentes e o limite de chamadas não tiver sido atingido.
- Libera o lease mesmo quando uma chamada falha.
Cada página usa uma chave de tentativa formada pelo run, cliente e ultNSU solicitado. Se um step for repetido, uma tentativa já persistida é reutilizada em vez de consultar e gravar a mesma página novamente.
Decisões por resposta
| Resposta | Comportamento |
|---|---|
cStat=138 | Há documentos localizados. Exige ultNSU maior que o solicitado, persiste a página, avança o cursor e continua quando ultNSU < maxNSU. |
cStat=137 | Não há novos documentos. Exige ultNSU = maxNSU, persiste a tentativa, avança o cursor e encerra o cliente. |
cStat=656 | Consumo indevido. Registra o bloqueio por uma hora, preserva o cursor e encerra o cliente nesta execução. |
Outro cStat | Registra falha, preserva o cursor e encerra o cliente. |
Falhas de transporte, HTTP não 2xx, XML inválido, campos de cursor ausentes, NSU regressivo e documentos com o mesmo NSU mas conteúdo diferente também preservam current_ult_nsu. Essa regra impede que uma página seja perdida sem ter sido persistida.
O que existe dentro de docZip
Na resposta de distribuição, cada docZip possui os atributos NSU e schema. Seu conteúdo é um XML compactado com gzip e depois codificado em Base64. Portanto, schema identifica o XML obtido depois de decodificar o Base64 e descompactar o gzip.
| Schema | Documento recuperado |
|---|---|
procEventoNFe_v1.00.xsd | Evento processado da NF-e. Referencia uma nota, mas não contém o XML completo da nota fiscal. |
procNFe_v4.00.xsd | NF-e completa processada, formada pelo XML da nota e seu protocolo de autorização. |
resNFe_v1.01.xsd | Resumo da NF-e, com um conjunto limitado de dados de identificação e valores. |
Um procEventoNFe_v1.00.xsd com tpEvento=210200 representa Confirmação da Operação, um evento de manifestação do destinatário. A presença de chNFe somente vincula o evento à NF-e; ela não transforma o documento de evento no conteúdo da nota.
Classifique antes de consumir
Não trate todo docZip como NF-e completa. Leia schema e, para eventos, tpEvento antes de decidir como apresentar ou processar o XML.
O workflow atual não descompacta nem interpreta o XML interno. Ele guarda NSU, schema e o docZip original em Base64 para consumo posterior.
Manifestação do destinatário
A manifestação do destinatário é um evento oficial enviado à SEFAZ pela empresa que consta como destinatária da NF-e. Uma pessoa autorizada, o sistema fiscal ou o ERP da empresa pode realizar essa ação, mas o evento precisa ser assinado com o certificado digital cujo CNPJ-base corresponde ao destinatário.
| Código | Manifestação | Significado |
|---|---|---|
210200 | Confirmação da Operação | Confirma que a operação e, quando aplicável, o recebimento da mercadoria ocorreram. É uma declaração conclusiva e impede o emitente de cancelar a NF-e. |
210210 | Ciência da Operação ou Ciência da Emissão | Declara que o destinatário sabe que a NF-e existe, mas ainda não possui elementos para concluir se a operação ocorreu. É uma manifestação preliminar e deve ser seguida posteriormente por uma manifestação conclusiva. |
210220 | Desconhecimento da Operação | Declara que o destinatário não solicitou nem reconhece a operação. Essa opção não libera a NF-e completa para download. |
210240 | Operação não Realizada | Reconhece a participação do destinatário, mas declara que a operação não foi concluída, como em uma recusa de recebimento. Exige justificativa. |
Antes da manifestação, o Ambiente Nacional normalmente distribui ao destinatário apenas o resNFe, sem os produtos da nota. Depois de uma manifestação aceita diferente de Desconhecimento da Operação, a SEFAZ disponibiliza a NF-e completa desta forma:
- O destinatário recebe um
resNFecom a chave e os dados resumidos. - Uma pessoa ou sistema autorizado envia a manifestação pelo serviço
NFeRecepcaoEvento. - O Ambiente Nacional registra o evento e gera um novo NSU referenciando a NF-e completa.
- Uma consulta posterior ao
NFeDistribuicaoDFerecupera um novodocZipcom schemaprocNFe. - O
resNFeoriginal permanece armazenado; ele não é substituído pelo documento completo.
Não confirme apenas para liberar o XML
Confirmação da Operação possui efeito fiscal e declara que a operação realmente ocorreu. Quando a empresa ainda precisa analisar a nota ou a entrega, Ciência da Operação é a manifestação preliminar apropriada para liberar o XML, devendo depois ser seguida pela manifestação conclusiva correspondente.
O workflow atual apenas consulta e persiste a distribuição. Ele não envia manifestações. Portanto, os documentos completos encontrados no NFE_DB foram liberados porque uma pessoa ou outro sistema manifestou essas notas, ou porque o CNPJ consultado tinha outro papel autorizado na NF-e, como transportador ou terceiro informado em autXML.
Persistência e observabilidade
O banco compartilhado DB guarda workflow_runs e workflow_run_events. O banco fiscal NFE_DB guarda:
nfe_clients: configuração, cursor, bloqueio, lease e último resultado por cliente.nfe_distribution_attempts: requisição, resposta, status e cursores de cada página.nfe_documents:NSU,schemaedocZipvinculados à tentativa.nfe_import_daily_counts: total diário de documentos inéditos por cliente.
O monitor mostra clientes, runs, steps e diagnósticos do proxy. A API entrega os documentos persistidos por GET /nfe/distributions, com autenticação obrigatória, escopo definido por clienttoken e paginação por cursor do maior NSU para o menor. O tamanho da página permanece limitado a 200 documentos. Esse endpoint ignora a cota de taxa/janela de requisições da chave de API; porém, quando a chave possui uma cota finita de usos restantes, cada chamada ainda consome essa cota, e seu limite continua sendo aplicado, retornando 429 após o esgotamento. Cursores criados antes da ordenação decrescente devem ser descartados.
Download autenticado de XML e DANFE
Os downloads usam o identificador retornado por GET /nfe/distributions:
GET /nfe/{documentId}/xml?clienttoken={clienttoken}
GET /nfe/{documentId}/danfe?clienttoken={clienttoken}As duas rotas exigem a autenticação principal da API e um clienttoken válido. A busca combina o identificador do documento com o cliente resolvido pelo token; por isso, um documento desconhecido e um documento pertencente a outro cliente recebem a mesma resposta 404. Assim como a listagem, os downloads ignoram apenas a cota de taxa/janela da chave de API. Uma cota finita de usos restantes continua sendo consumida e retorna 429 quando se esgota.
O download XML decodifica o Base64 e descompacta o gzip sob demanda, valida UTF-8 e XML, e devolve exatamente os bytes descompactados. Ele aceita documentos completos procNFe, resumos resNFe e eventos procEventoNFe. A resposta usa application/xml; charset=utf-8, anexo <chave-de-acesso-ou-NSU>.xml e Cache-Control: private, no-store.
O DANFE aceita somente um procNFe completo, contendo nfeProc/NFe/infNFe. Resumos, eventos e documentos procNFe incompletos retornam 422; eventos posteriores não são agregados à nota. O XML é enviado transitoriamente ao Browser Run, o PDF é validado e devolvido como application/pdf, anexo danfe-<chave-de-acesso-ou-NSU>.pdf e Cache-Control: private, no-store.
Saída transitória
XML e DANFE são gerados novamente a cada requisição. A API não grava o XML descompactado nem o PDF em D1, R2, KV ou no sistema de arquivos.
O renderer global aceita no máximo cinco DANFEs simultâneos. A sexta tentativa não entra em fila: recebe 503, Retry-After com segundos inteiros positivos e pode ser repetida pelo cliente depois desse intervalo. Cada render possui limite de 20 segundos; o XML descompactado pode ter no máximo 10 MiB e o PDF, 20 MiB. O retorno só é aceito quando começa com a assinatura %PDF-.
| HTTP | Corpo JSON estável | Quando ocorre |
|---|---|---|
400 | { "error": "Invalid clienttoken" } | clienttoken ausente, vazio, desconhecido ou ambíguo. |
401 | { "error": "Unauthorized" } | Credencial principal ausente, inválida, desabilitada ou expirada. |
404 | { "error": "NF-e document not found" } | Documento desconhecido ou pertencente a outro cliente. |
422 | { "error": "DANFE unavailable for this document type" } | A rota DANFE recebeu resumo, evento ou procNFe incompleto. |
429 | { "error": "Too many requests" } | A cota finita de usos restantes da chave de API foi esgotada. |
500 | { "error": "Invalid stored NF-e document" } | O docZip persistido não pode ser decodificado, descompactado ou validado dentro dos limites. |
502 | { "error": "DANFE generation failed" } | O Browser Run ou o renderer falhou, rejeitou o PDF ou encerrou inesperadamente. |
503 | { "error": "DANFE renderer busy" } | Os cinco slots globais estão ocupados ou o Browser Run informou indisponibilidade; inclui Retry-After. |
504 | { "error": "DANFE generation timed out" } | A geração excedeu 20 segundos. |
Como diagnosticar uma execução
- Abra o run de
nfe-distributionno monitor e identifique o cliente e orequestedUltNsudo step que falhou. - Consulte
workflow_run_eventspara ver classificação, avanço de cursor e diagnóstico do proxy. - Confira
nfe_clients.current_ult_nsu,blocked_until,last_error_codeelast_error_message. - Localize a página em
nfe_distribution_attemptspelo run, cliente erequested_ult_nsu. - Confira os documentos relacionados em
nfe_documentsantes de considerar qualquer ajuste manual de cursor.
Um run com processedClients: 0 pode ser saudável quando nenhum cliente está elegível. last_error_code=656 com blocked_until no futuro indica bloqueio temporário por consumo indevido, não um workflow travado.
Referência fiscal
- A estrutura de
retDistDFeInt, os atributos dedocZip, a compactação gzip/Base64, os schemas distribuídos e o fluxo de liberação da NF-e estão documentados no Manual de Orientação do Contribuinte da NF-e — NFeDistribuicaoDFe. - Os eventos, códigos, assinaturas e significados da manifestação estão documentados no Manual de Orientação do Contribuinte da NF-e — Manifestação do Destinatário.