API de nota fiscal emite, mas quem guarda o XML autorizado é você
Depois que a API de emissão autoriza a NF-e, baixe o XML com o protocolo, confira a chave, grave com hash em armazenamento seu e com cópia, e trate cancelamentos e cartas de correção do mesmo jeito.
Um cliente da distribuidora pede o XML de uma nota de oito meses atrás para a contabilidade dele. A equipe abre o sistema e encontra só o número da nota e um status "autorizada". O XML ficou no painel da API de emissão, que a empresa trocou por outro provedor há três meses. A API emite a nota por você; a obrigação de guardar o XML continua sua.
A resposta curta: assim que a nota for autorizada, baixe o XML completo (a nota com o protocolo de autorização), confira que ele corresponde à chave esperada, grave em armazenamento sob seu controle com o hash registrado no banco e mantenha uma segunda cópia. Faça o mesmo com os XMLs de eventos, como cancelamento e carta de correção. O painel do provedor é conveniência, não arquivo.
Por que o sucesso da API não basta
Serviços de emissão de NF-e, como o do repositório de exemplo em C# que originou este post, cuidam de montar, assinar e transmitir a nota à SEFAZ. A resposta de sucesso traz a chave de acesso, o status e, em geral, um jeito de obter o XML e o DANFE. É tentador gravar só a chave e o status e seguir em frente, porque "está tudo lá no provedor".
O problema é que o provedor é um fornecedor. O contrato pode acabar, a conta pode ser encerrada, o serviço pode ter prazo próprio de retenção ou simplesmente sair do ar. A legislação fiscal exige que o emitente guarde os XMLs pelo prazo legal e os apresente quando solicitado; confirme com o seu contador qual é esse prazo e em que formato guardar. Se o único lugar onde o arquivo existe é o painel de alguém, você não tem o arquivo. Isto não substitui a orientação de um contador; serve para o sistema não depender da memória de terceiros.
O post sobre biblioteca de nota fiscal não substituir o contador trata da parte de obrigação. Aqui o foco é técnico: o que gravar, quando e como provar que o arquivo guardado é o certo.
O que precisa ser guardado
- O XML de distribuição da nota, com a raiz
nfeProc, que reúne a nota assinada (NFe) e o protocolo de autorização (protNFe). A nota sem o protocolo não prova que foi autorizada. - Os XMLs de eventos: cancelamento, carta de correção e outros que o seu fluxo usar, também com o retorno de registro.
- Os metadados no banco: chave de acesso, número do protocolo, data de autorização, hash do arquivo e onde ele está.
O DANFE em PDF pode ser regerado a partir do XML; o XML não pode ser regerado a partir do PDF. A prioridade é clara.
Como guardar o XML autorizado, passo a passo
- Ao receber a autorização, baixe o XML completo pelo meio que o provedor oferece. Faça isso no mesmo fluxo, ou numa fila com nova tentativa, nunca "depois, se lembrar".
- Confira o conteúdo: o atributo
IddoinfNFedeve ser "NFe" seguido da chave esperada, e o protocolo deve indicar uso autorizado. - Grave o arquivo num caminho previsível, organizado pelo ano e mês que já estão na chave.
- Registre no banco a chave, o protocolo, o hash SHA-256 e o caminho.
- Replique para um segundo local, de preferência com versionamento ou retenção que impeça apagar por engano.
- Repita para cada evento da nota.
- Teste a recuperação: periodicamente, sorteie notas, leia o arquivo e confira o hash.
Exemplo em C#
O código abaixo recebe a chave e o endereço do XML devolvido pelo provedor. O nome exato do campo na resposta depende do serviço; consulte a documentação dele. A partir do XML, tudo é padrão da NF-e: namespace, elementos e o código de status.
using System.Security.Cryptography;
using System.Xml.Linq;
public sealed class ArquivoFiscal
{
private static readonly XNamespace Nfe = "http://www.portalfiscal.inf.br/nfe";
private static readonly HashSet<string> Autorizada = new() { "100", "150" };
private readonly HttpClient _http;
private readonly AppDbContext _db;
private readonly string _raiz;
public ArquivoFiscal(HttpClient http, AppDbContext db, string raiz)
{
_http = http;
_db = db;
_raiz = raiz;
}
public async Task GuardarAsync(string chave, Uri urlXml, CancellationToken ct)
{
byte[] conteudo = await _http.GetByteArrayAsync(urlXml, ct);
XDocument doc;
using (var stream = new MemoryStream(conteudo))
{
doc = await XDocument.LoadAsync(stream, LoadOptions.None, ct);
}
string? id = doc.Descendants(Nfe + "infNFe").FirstOrDefault()?.Attribute("Id")?.Value;
XElement? prot = doc.Descendants(Nfe + "infProt").FirstOrDefault();
string? cStat = prot?.Element(Nfe + "cStat")?.Value;
string? protocolo = prot?.Element(Nfe + "nProt")?.Value;
if (id != "NFe" + chave)
{
throw new InvalidOperationException("XML recebido não corresponde à chave " + chave);
}
if (cStat is null || !Autorizada.Contains(cStat) || protocolo is null)
{
throw new InvalidOperationException("XML sem protocolo de autorização para " + chave);
}
string hash = Convert.ToHexString(SHA256.HashData(conteudo));
string anoMes = chave.Substring(2, 4);
string pasta = Path.Combine(_raiz, anoMes);
Directory.CreateDirectory(pasta);
string destino = Path.Combine(pasta, chave + "-procNFe.xml");
string temporario = destino + ".tmp";
await File.WriteAllBytesAsync(temporario, conteudo, ct);
File.Move(temporario, destino, overwrite: true);
_db.XmlsFiscais.Add(new XmlFiscal
{
Chave = chave,
Protocolo = protocolo,
Sha256 = hash,
Caminho = destino,
GuardadoEm = DateTimeOffset.UtcNow
});
await _db.SaveChangesAsync(ct);
}
}
Alguns pontos do exemplo. A chave de acesso tem 44 dígitos, e as posições 3 a 6 trazem ano e mês de emissão, o que dá a organização das pastas. O código 100 indica uso autorizado; o 150 indica autorização fora de prazo, que também é nota válida. A gravação vai primeiro para um arquivo temporário e depois é movida, para que uma falha no meio não deixe um XML pela metade com o nome final. Se qualquer conferência falhar, a exceção deve levar a nota para uma fila de pendências visível, não sumir num log.
Em produção, troque o disco local por um armazenamento de objetos com versionamento e uma política de retenção, e mantenha a segunda cópia em outra conta ou região. O raciocínio é o mesmo de um backup de banco: só vale o que você já testou restaurar, como explica o post sobre backup que restaura de verdade.
Credenciais e ambiente
O certificado digital A1 e o token da API são segredos. Não ficam no repositório, nem no projeto de exemplo que você clonou para testar; vão para o gerenciador de segredos ou variáveis de ambiente do servidor. Não imprima o token nem a resposta completa no log de sucesso, como alerta o post sobre log sem senha. Se a chave vazar, troque no provedor antes de qualquer outra coisa. E feche todo o fluxo, inclusive a guarda do XML, no ambiente de homologação antes de emitir a primeira nota real.
Erros comuns
- Guardar só a chave e o status: quando o provedor sai do ar ou o contrato acaba, não há XML para entregar ao cliente nem ao fisco.
- Guardar a nota sem o protocolo: o arquivo não prova que a nota foi autorizada.
- Baixar o XML "depois", fora do fluxo: falhas silenciosas deixam buracos que só aparecem meses depois.
- Esquecer os eventos: o cancelamento existe na SEFAZ, mas o seu arquivo mostra a nota como válida.
- Uma cópia só, no mesmo servidor: o disco que falha leva o histórico fiscal junto.
- Certificado e token no repositório: quem tem acesso ao código pode emitir nota em nome da empresa.
Checklist
- Toda nota autorizada tem o XML com
protNFegravado em armazenamento seu. - O sistema confere chave e código de status antes de gravar.
- Banco registra chave, protocolo, hash e caminho de cada arquivo.
- Existe uma segunda cópia com retenção ou versionamento.
- Eventos de cancelamento e correção são guardados do mesmo jeito.
- Falhas de download caem numa fila de pendências visível.
- Certificado e token estão fora do repositório e fora dos logs.
- Uma verificação periódica sorteia notas e confere o hash dos arquivos.
Faça o exercício agora: se a conta no provedor de emissão fosse encerrada hoje, você conseguiria entregar o XML da primeira nota que o sistema emitiu?
Referência: https://github.com/webmaniabr/NFe-C-Sharp