Emissor NF-e em .NET para geração, assinatura, autorização em homologação/produção, eventos fiscais, consulta SEFAZ e DANFE, com API stateless, CLI e pacotes NuGet.
Status: projeto em evolução. A emissão em homologação e produção já foram testadas, mas o uso em produção exige validação fiscal, jurídica e operacional no cenário da sua empresa.
Sobre a responsabilidade pelo uso
Este projeto nasceu de uma necessidade real e foi construído com cuidado, mas NF-e é um ecossistema complexo. Regras tributárias mudam, cada UF tem suas peculiaridades, cada regime fiscal tem suas exigências, e cada empresa tem um cenário diferente dos demais.
Antes de usar em produção: teste muito. Valide no ambiente de homologação da SEFAZ. Revise os XMLs gerados com alguém que entenda do processo fiscal da sua empresa.
A legislação, as regras de CSOSN/CST, os parâmetros de ICMS, PIS e COFINS variam por UF, regime tributário e atividade econômica, e essa variação é sua responsabilidade conhecer e configurar corretamente.
Ao usar este projeto, você assume total responsabilidade pelos documentos emitidos. O autor disponibiliza o código de boa-fé, mas não presta suporte fiscal nem se responsabiliza por erros, rejeições ou penalidades.
Este projeto emite NF-e modelo 55 usando .NET, certificado digital A1 e webservices da SEFAZ.
Ele possui:
Nfe.Api: API HTTP para emissão assíncrona, eventos fiscais, consulta de status e consulta da chave na SEFAZ.Nfe.Core: geração de XML, assinatura digital, envio para SEFAZ, eventos e validações.Nfe.Cli: utilitário local para gerar e assinar XML de NF-e, eventos e inutilização sem enviar para a SEFAZ.
Dependências principais:
NFEConsulta: usada para consulta de status do serviço e consulta de NF-e pela chave de acesso.NFEDanfe: usada para geração de DANFE em PDF a partir de XML autorizado (procNFe.xml).NFeSchemaDownloader: usada pela API para sincronizar schemas XSD oficiais quando necessário.
O empacotamento é separado por responsabilidade:
NFEEmissor: biblioteca principal para geração, assinatura e autorização.NFEEmissor.Cli: ferramentadotnet toolcom o comandonfe-emissorpara gerar XML assinado localmente.
Nfe.Api não é empacotado como NuGet; ele é uma aplicação HTTP para rodar via Docker ou publicação própria.
Para empacotar localmente:
dotnet pack NfeEmissor.Packages.slnx -o ./artifacts/packagesPara instalar o CLI como tool a partir de um pacote local:
dotnet tool install --global NFEEmissor.Cli \
--add-source ./artifacts/packages \
--version 0.3.2Depois de instalado:
nfe-emissor --helpQuando os pacotes estiverem publicados no NuGet:
dotnet add package NFEEmissor --version 0.3.2
dotnet tool install --global NFEEmissor.Cli --version 0.3.2Licença: MIT.
- Docker e Docker Compose.
- Certificado digital A1 em PEM ou PFX.
- Dados fiscais reais e coerentes com o certificado.
Os certificados devem ficar em certs/. Essa pasta é ignorada pelo Git.
Exemplo esperado:
certs/cert.pem
certs/key.pem
certs/cert.pfx
docker compose up -d --buildA API fica disponível em:
http://localhost:5000
Serviços auxiliares:
- Redis:
localhost:6379 - Seq:
http://localhost:8085
Healthcheck:
curl -sS "http://localhost:5000/health"A API foi desenhada para não ser o repositório definitivo dos documentos fiscais.
- Não há banco de dados obrigatório.
- Redis é usado apenas para fila, idempotência curta, backoff temporário da SEFAZ e status com TTL.
- Certificados e senhas colocados na fila Redis são protegidos com AES-256-GCM antes de serem serializados.
- O status retorna o
xmlResult(procNFe.xml) e, quando solicitado,danfePdfBase64. - A aplicação cliente deve persistir o XML autorizado e o DANFE em seu próprio storage, banco, disco, S3/MinIO ou sistema fiscal.
- Para integrar persistência sem mudar o fluxo da API, implemente
INfeStorage. A implementação padrão éNoopNfeStorage, que não grava nada.
Por padrão, o resultado temporário expira em 12 horas. Depois disso, a API pode retornar 404 para o correlationId.
Para produção ou múltiplas réplicas da API, configure uma chave compartilhada para proteger as credenciais temporárias da fila:
export Nfe__QueueProtectionKey="use-um-segredo-com-pelo-menos-32-caracteres"Sem essa configuração, a aplicação usa uma chave aleatória local gerada ao iniciar o processo. Isso é suficiente para desenvolvimento em uma única instância, mas mensagens antigas da fila não poderão ser processadas após restart.
Os exemplos abaixo usam http://localhost:5000 e cobrem os principais endpoints:
POST /api/v1/nfe/emitirGET /api/v1/nfe/status/{correlationId}GET /api/v1/nfe/consultaGET /api/v1/nfe/status-servicoPOST /api/v1/nfe/cancelarPOST /api/v1/nfe/ccePOST /api/v1/nfe/inutilizarPOST /api/v1/certificado/infoGET /healthGET /api/v1/nfe/schemas
Use o arquivo nota-teste.json como base. Em homologação, mantenha:
{
"ambienteEmissao": "2",
"destinatario": {
"nomeRazaoSocial": "NF-E EMITIDA EM AMBIENTE DE HOMOLOGACAO - SEM VALOR FISCAL"
}
}CERT=$(base64 -w0 certs/cert.pem)
KEY=$(base64 -w0 certs/key.pem)
curl -sS -X POST "http://localhost:5000/api/v1/nfe/emitir?gerarDanfe=false" \
-H "Content-Type: application/json" \
-H "X-Cert-Pem-Base64: $CERT" \
-H "X-Key-Pem-Base64: $KEY" \
--data-binary @nota-teste.jsonResposta esperada:
{
"correlationId": "3f8a5d63ad894b998b810e509fdf9c4c",
"status": "Pendente",
"message": "A nota fiscal foi colocada na fila de processamento."
}CERT=$(base64 -w0 certs/cert.pfx)
curl -sS -X POST "http://localhost:5000/api/v1/nfe/emitir?gerarDanfe=false" \
-H "Content-Type: application/json" \
-H "X-Certificado-Base64: $CERT" \
-H "X-Certificado-Senha: sua-senha" \
--data-binary @nota-teste.jsonDepois de emitir, consulte pelo correlationId retornado:
curl -sS "http://localhost:5000/api/v1/nfe/status/3f8a5d63ad894b998b810e509fdf9c4c"Resposta autorizada:
{
"correlationId": "3f8a5d63ad894b998b810e509fdf9c4c",
"status": "Autorizada",
"chaveAcesso": "35260612345678000195550010000000011000000010",
"protocolo": "135000000000000",
"xmlResult": "<?xml version=\"1.0\" encoding=\"utf-8\"?><nfeProc ...",
"danfePdfBase64": "JVBERi0xLjQK...",
"expiraEm": "2026-06-25T18:00:00+00:00",
"ttlSegundos": 43200,
"storage": {
"persistido": false,
"xmlProcNfeUri": null,
"danfePdfUri": null
}
}Salve xmlResult como procNFe.xml. Se danfePdfBase64 vier preenchido, decodifique o Base64 e salve como PDF.
Use o endpoint de consulta quando você já tiver uma chave NF-e de 44 dígitos.
CERT=$(base64 -w0 certs/cert.pem)
KEY=$(base64 -w0 certs/key.pem)
curl -sS "http://localhost:5000/api/v1/nfe/consulta?chave=35260612345678000195550010000000011000000010&uf=SP&ambiente=2" \
-H "X-Cert-Pem-Base64: $CERT" \
-H "X-Key-Pem-Base64: $KEY"Resposta:
{
"status": "100",
"motivo": "Autorizado o uso da NF-e",
"protocolo": "135000000000000",
"xmlRetorno": null
}Troque ambiente=2 por ambiente=1:
curl -sS "http://localhost:5000/api/v1/nfe/consulta?chave=SUA_CHAVE&uf=SP&ambiente=1" \
-H "X-Cert-Pem-Base64: $CERT" \
-H "X-Key-Pem-Base64: $KEY"CERT=$(base64 -w0 certs/cert.pfx)
curl -sS "http://localhost:5000/api/v1/nfe/status-servico?uf=SP&ambiente=2" \
-H "X-Certificado-Base64: $CERT" \
-H "X-Certificado-Senha: sua-senha"Se preferir PEM, o endpoint também aceita X-Cert-Pem-Base64 e X-Key-Pem-Base64 no lugar do PFX.
Os eventos fiscais usam os mesmos headers de certificado da emissão e consulta. A API aceita PFX:
CERT=$(base64 -w0 certs/cert.pfx)Ou PEM:
CERT=$(base64 -w0 certs/cert.pem)
KEY=$(base64 -w0 certs/key.pem)Use o cancelamento quando a NF-e já foi autorizada e ainda está dentro das regras/prazo da SEFAZ.
curl -sS -X POST "http://localhost:5000/api/v1/nfe/cancelar" \
-H "Content-Type: application/json" \
-H "X-Certificado-Base64: $CERT" \
-H "X-Certificado-Senha: sua-senha" \
-d '{
"ambiente": "2",
"uf": "SP",
"chaveAcesso": "35260612345678000195550010000000011000000010",
"cnpjEmitente": "12345678000195",
"protocoloAutorizacao": "135000000000000",
"justificativa": "Erro operacional identificado apos autorizacao"
}'Resposta autorizada:
{
"status": "135",
"motivo": "Evento registrado e vinculado a NF-e",
"chaveAcesso": "35260612345678000195550010000000011000000010",
"tipoEvento": "110111",
"sequenciaEvento": 1,
"protocolo": "135000000000001",
"xmlProcEventoNfe": "<procEventoNFe ..."
}Use CC-e apenas para correções permitidas pela legislação. Ela não pode corrigir valores de imposto, remetente/destinatário, data de emissão ou saída.
curl -sS -X POST "http://localhost:5000/api/v1/nfe/cce" \
-H "Content-Type: application/json" \
-H "X-Cert-Pem-Base64: $CERT" \
-H "X-Key-Pem-Base64: $KEY" \
-d '{
"ambiente": "2",
"uf": "SP",
"chaveAcesso": "35260612345678000195550010000000011000000010",
"cnpjEmitente": "12345678000195",
"sequenciaEvento": 1,
"correcao": "Correção do texto das informações adicionais da nota fiscal"
}'Use inutilização para comunicar uma quebra de sequência de numeração que não será usada.
curl -sS -X POST "http://localhost:5000/api/v1/nfe/inutilizar" \
-H "Content-Type: application/json" \
-H "X-Certificado-Base64: $CERT" \
-H "X-Certificado-Senha: sua-senha" \
-d '{
"ambiente": "2",
"uf": "SP",
"cnpjEmitente": "12345678000195",
"ano": "2026",
"modelo": "55",
"serie": "1",
"numeroInicial": 10,
"numeroFinal": 12,
"justificativa": "Quebra de sequência por erro operacional interno"
}'Resposta autorizada:
{
"status": "102",
"motivo": "Inutilizacao de numero homologado",
"uf": "35",
"ano": "26",
"cnpjEmitente": "12345678000195",
"serie": "1",
"numeroInicial": 10,
"numeroFinal": 12,
"protocolo": "135000000000002"
}CERT=$(base64 -w0 certs/cert.pfx)
curl -sS -X POST "http://localhost:5000/api/v1/certificado/info" \
-H "X-Certificado-Base64: $CERT" \
-H "X-Certificado-Senha: sua-senha"Use o CLI quando quiser apenas gerar e assinar o XML localmente.
Com PEM:
docker run --rm \
-v "$PWD:/src" \
-w /src \
mcr.microsoft.com/dotnet/sdk:10.0 \
dotnet run --project src/Nfe.Cli/Nfe.Cli.csproj -- \
emitir \
--json nota-teste.json \
--cert certs/cert.pem \
--key certs/key.pem \
--output-dir outCom PFX:
docker run --rm \
-v "$PWD:/src" \
-w /src \
mcr.microsoft.com/dotnet/sdk:10.0 \
dotnet run --project src/Nfe.Cli/Nfe.Cli.csproj -- \
emitir \
--json nota-teste.json \
--cert certs/cert.pfx \
--senha sua-senha \
--output-dir outO XML assinado será salvo em out/.
O CLI também gera XML assinado de eventos e inutilização sem enviar para a SEFAZ:
dotnet run --project src/Nfe.Cli/Nfe.Cli.csproj -- \
cancelar \
--json cancelamento.json \
--cert certs/cert.pfx \
--senha sua-senha \
--output-dir out
dotnet run --project src/Nfe.Cli/Nfe.Cli.csproj -- \
cce \
--json cce.json \
--cert certs/cert.pem \
--key certs/key.pem \
--output-dir out
dotnet run --project src/Nfe.Cli/Nfe.Cli.csproj -- \
inutilizar \
--json inutilizacao.json \
--cert certs/cert.pfx \
--senha sua-senha \
--output-dir outUse um XML autorizado/processado (*-procNFe.xml). XML apenas assinado, sem protocolo de autorização, não é suficiente para um DANFE fiscalmente válido.
Na API, informe gerarDanfe=true ao emitir:
curl -sS -X POST "http://localhost:5000/api/v1/nfe/emitir?gerarDanfe=true" \
-H "Content-Type: application/json" \
-H "X-Cert-Pem-Base64: $CERT" \
-H "X-Key-Pem-Base64: $KEY" \
--data-binary @nota-teste.jsonO status retornará danfePdfBase64. Decodifique esse valor e salve como PDF na aplicação cliente.
A geração de PDF usa QuestPDF por meio da dependência NFEDanfe; o projeto configura a licença como LicenseType.Community antes de gerar o PDF:
QuestPDF.Settings.License = LicenseType.Community;O pacote NFEEmissor.Cli não inclui geração de DANFE para evitar um pacote de ferramenta muito grande. Use a API ou a dependência NFEDanfe diretamente para DANFE.
O projeto aceita CNPJ com letras, preservando os 14 caracteres alfanuméricos no XML e na chave de acesso. Pontuação é removida automaticamente:
{
"cnpj": "12.ABC.345/0001-88"
}Também há suporte inicial ao grupo IBSCBS nos impostos do item. O projeto escreve os campos informados e agrega os totais em IBSCBSTot, mas não calcula automaticamente enquadramento, CST, cClassTrib ou alíquotas. Esses valores devem vir do sistema fiscal/tributário do emissor.
O validador XSD pode ser apontado para pacotes de schema mais recentes, como PL_010C/CNPJ Alfa, por configuração:
export Nfe__SchemasPath="/caminho/para/schemas/v4"
export Nfe__TiposBasicosSchema="tiposBasico_PL_010C_v1.30.xsd"
export Nfe__NfeSchema="nfe_PL_010C_v1.30.xsd"
export Nfe__ValidateXsdBeforeSend="true"Se os nomes não forem informados, o projeto tenta descobrir automaticamente tiposBasico*.xsd e nfe_v*.xsd no diretório configurado.
Na API, Nfe__ValidateXsdBeforeSend vem habilitado por padrão. Se os schemas não estiverem disponíveis ou o XML não validar, a NF-e não é assinada nem enviada para a SEFAZ. Para diagnosticar quais schemas foram carregados:
curl -sS "http://localhost:5000/api/v1/nfe/schemas"No CLI, a validação XSD é explícita:
nfe-emissor emitir \
--json nota-teste.json \
--cert certs/cert.pfx \
--senha sua-senha \
--validar-xsd \
--schemas-path schemas/v4 \
--tipos-basicos-schema tiposBasico_PL_010C_v1.30.xsd \
--nfe-schema nfe_PL_010C_v1.30.xsdExemplo:
{
"impostos": {
"ibsCbs": {
"cst": "410",
"codigoClassificacaoTributaria": "410999",
"baseCalculo": 100.0,
"ibsUf": {
"aliquota": 0.1,
"valor": 0.1
},
"ibsMunicipio": {
"aliquota": 0.0,
"valor": 0.0
},
"cbs": {
"aliquota": 0.9,
"valor": 0.9
}
}
}
}Quando codigoEan ou codigoEanTributavel forem informados, o projeto valida GTIN-8, GTIN-12, GTIN-13 ou GTIN-14 pelo dígito verificador antes de gerar o XML. Quando o produto não possuir GTIN, omita o campo ou informe SEM GTIN.
Exemplo:
{
"codigoEan": "7891234567895",
"codigoEanTributavel": "7891234567895"
}{
"ambienteEmissao": "2",
"serie": "1",
"numeroNfe": 8,
"naturezaOperacao": "VENDA DE MERCADORIA",
"tipoOperacao": "1",
"formatoImpressaoDanfe": "1",
"tipoEmissao": "1",
"finalidadeEmissao": "1",
"consumidorFinal": "1",
"indicadorPresencaComprador": "9",
"emitente": {
"cnpj": "12345678000195",
"razaoSocial": "EMPRESA EMITENTE TESTE LTDA",
"nomeFantasia": "EMPRESA EMITENTE TESTE LTDA",
"inscricaoEstadual": "110042490114",
"cnaeFiscal": "2500000",
"codigoRegimeTributario": "3",
"endereco": {
"logradouro": "RUA TESTE",
"numero": "100",
"bairro": "CENTRO",
"codigoMunicipio": "3547809",
"nomeMunicipio": "SAO PAULO",
"uf": "SP",
"cep": "01001000",
"codigoPais": "1058",
"nomePais": "BRASIL"
}
},
"destinatario": {
"cnpj": "99999999000191",
"nomeRazaoSocial": "NF-E EMITIDA EM AMBIENTE DE HOMOLOGACAO - SEM VALOR FISCAL",
"indicadorIe": "9",
"endereco": {
"logradouro": "AVENIDA CLIENTE",
"numero": "200",
"bairro": "JARDINS",
"codigoMunicipio": "3550308",
"nomeMunicipio": "SAO PAULO",
"uf": "SP",
"cep": "02002000",
"codigoPais": "1058",
"nomePais": "Brasil"
}
},
"produtos": [
{
"codigoProduto": "EB.007",
"descricao": "CACAMBA No2 METALICA ONDULADA 1040X959X660 VW.00001 TARA 70KG",
"ncmSh": "73090090",
"cfop": "5102",
"unidadeComercial": "PC",
"quantidadeComercial": 3.0,
"valorUnitarioComercial": 500.0,
"valorBruto": 1500.0,
"unidadeTributavel": "PC",
"quantidadeTributavel": 3.0,
"valorUnitarioTributavel": 500.0,
"indicadorComposicaoTotal": "1",
"impostos": {
"icms": {
"cst": "00",
"origem": "0",
"baseCalculo": 1500.0,
"aliquota": 18.0,
"valor": 270.0
},
"pis": {
"cst": "01",
"baseCalculo": 1500.0,
"aliquota": 1.65,
"valor": 24.75
},
"cofins": {
"cst": "01",
"baseCalculo": 1500.0,
"aliquota": 7.6,
"valor": 114.0
}
}
}
],
"transporte": {
"modalidadeFrete": "9"
},
"pagamentos": [
{
"meioPagamento": "15",
"valor": 1500.0
}
]
}ambienteEmissao:1para produção,2para homologação.indicadorComposicaoTotal:1compõe o total da NF-e,0não compõe.- Em homologação, a razão social do destinatário deve ser
NF-E EMITIDA EM AMBIENTE DE HOMOLOGACAO - SEM VALOR FISCAL. - CST com benefício fiscal pode exigir
cBenef, conforme regra da UF. - O projeto aplica backoff temporário quando a SEFAZ retorna
656 - Consumo Indevido. certs/,out/,tmp-nfe-out/,schemas/e*-procNFe.xmlsão ignorados pelo Git.
| Projeto | Descrição |
|---|---|
| NFeSchemaDownloader | Mantém os Schemas XML (XSD) da SEFAZ sempre atualizados automaticamente |
| NFEConsulta | Consulta NF-e, valida XML e verifica status oficial da SEFAZ |
| NFEDanfe | Biblioteca .NET para gerar DANFE em PDF a partir de XML de NF-e autorizada. |
docker run --rm \
-v "$PWD:/src" \
-w /src \
mcr.microsoft.com/dotnet/sdk:10.0 \
dotnet test tests/Nfe.UnitTests/Nfe.UnitTests.csprojFabyo Guimarães Oliveira
- LinkedIn: https://www.linkedin.com/in/fabyo-guimaraes/
- GitHub: https://github.com/fabyo
