Como emitir NF-e com Python: certificado, assinatura e envio à SEFAZ

Emitir e consultar NF-e com Python: entenda certificado A1, assinatura XML com signxml, validação XSD, envio aos web services da SEFAZ, homologação e quando usar uma API.

04 Oct 2026 11 min de leitura Equipe Python Dev BR

Resposta rápida: para emitir NF-e com Python você precisa de quatro coisas: montar o XML no leiaute 4.00, assiná-lo com um certificado digital A1 (ICP-Brasil) usando XML Signature, validá-lo contra o XSD oficial e transmiti-lo aos web services da SEFAZ (nfeAutorizacao e nfeRetAutorizacao) via SOAP. Bibliotecas como nfelib, signxml e zeep cobrem o caminho manual; o PyNFe agrupa as etapas; APIs comerciais como NFE.io e Focus NFe emitem por você. Sem certificado válido, não existe emissão própria — só via API contratada.

Emitir nota fiscal é diferente de apenas ler notas recebidas. Se o seu objetivo é consolidar, conferir ou extrair dados de XML de NF-e que a empresa já recebeu, o guia de automação de XML de notas fiscais cobre esse lado. Aqui o assunto é o fluxo de emissão e consulta: transformar um pedido do seu sistema em uma nota autorizada, com responsabilidade sobre assinatura, transmissão e arquivamento.

Este guia é técnico e não substitui o contador ou o ERP. Regras fiscais de CFOP, ICMS, PIS/COFINS e regime tributário variam por empresa e por estado — a parte fiscal do XML deve nascer validada. O papel do Python é automatizar e integrar com segurança o que a legislação já define.

Como funciona a emissão de NF-e

A NF-e (modelo 55) segue um fluxo nacional, independentemente do estado emissor:

  1. Montagem do XML no leiaute 4.00, com a chave de acesso de 44 dígitos calculada a partir de UF, data, CNPJ, série, número e código numérico.
  2. Assinatura digital do nó infNFe com o certificado do emissor, usando XML Signature (RSA-SHA256).
  3. Validação contra o XSD oficial do pacote de schemas publicado pela SEFAZ.
  4. Transmissão do lote ao web service nfeAutorizacao do estado (ou do ambiente nacional/SVRS).
  5. Consulta do recibo em nfeRetAutorizacao até receber o protocolo de autorização (cStat 100) ou a rejeição.
  6. Arquivamento do nfeProc (NFe assinada + protocolo) e, se necessário, geração do DANFE para impressão.
EtapaQuem validaErro comum
Montar o XMLXSD + regras de negócioCFOP ou NCM errados, tributos inconsistentes
AssinarSEFAZ na recepçãoAssinar o nó errado ou alterar o XML depois de assinar
TransmitirWeb service da SEFAZLote fora do tamanho ou do formato esperado
ConsultarnfeRetAutorizacaoNão tratar rejeição e reenviar o mesmo número
ArquivarFiscal/auditoriaPerder o XML autorizado (a guarda é obrigatória)

Tudo isso roda primeiro em homologação (tpAmb=2), o ambiente de testes da SEFAZ, e só depois em produção (tpAmb=1). Nunca aponte o código para produção antes de validar em homologação.

NF-e, NFC-e e NFS-e: não confunda os documentos

Antes de escrever código, confirme qual documento o processo precisa emitir:

DocumentoModeloQuem autorizaUso típico
NF-e55SEFAZ estadual (ou ambiente nacional)Venda entre empresas, circulação de mercadorias
NFC-e65SEFAZ estadualVarejo, consumidor final, cupom eletrônico
NFS-e—Prefeitura municipalPrestação de serviços

A NF-e e a NFC-e compartilham boa parte do leiaute e dos web services. A NFS-e é outro mundo: cada prefeitura expõe integração própria (quando expõe), algumas seguem o padrão Abrasf, e há padrões como ABRASF e gim/DSF que exigem bibliotecas ou adaptações específicas. Se a tarefa é emitir nota de serviço, comece identificando o padrão da cidade antes de escolher biblioteca.

O que você precisa antes de programar

  • Certificado digital A1 (arquivo .pfx) ou A3 (token/smartcard) vinculado ao CNPJ emissor, emitido por autoridade ICP-Brasil. A1 é o mais prático para automação porque pode ser lido de arquivo.
  • Inscrição estadual e código de regime tributário válidos, definidos com a contabilidade.
  • Schemas XSD do leiaute vigente (pacote PL_009 da SEFAZ) para validar localmente o XML antes de gastar uma chamada de web service.
  • URLs dos web services do estado emissor, separadas para homologação e produção.

Convertendo o certificado A1 para PEM

O .pfx embute chave privada e certificado. Para usar em Python, exporte cada parte com o OpenSSL:

# chave privada (sem senha de arquivo; proteja o acesso ao disco e à senha de origem)
openssl pkcs12 -in certificado.pfx -nocerts -nodes -out chave_privada.pem

# certificado (parte pública)
openssl pkcs12 -in certificado.pfx -clcerts -nokeys -out certificado.pem

Guarde a senha do .pfx em variável de ambiente ou gerenciador de segredos — nunca no código. O artigo sobre segredos e tokens em Python mostra como estruturar isso sem vazar credenciais no repositório.

Montando o XML da nota

O coração da NF-e é o nó infNFe. Um exemplo mínimo (campos obrigatórios de uma operação realista, com tributos simplificados):

<infNFe Id="NFe35261012345678000190550010000011291234567890" versao="4.00">
  <ide>
    <cUF>35</cUF>
    <cNF>12345678</cNF>
    <natOp>VENDA DE MERCADORIA</natOp>
    <mod>55</mod>
    <serie>1</serie>
    <nNF>112</nNF>
    <dhEmi>2026-10-04T09:00:00-03:00</dhEmi>
    <tpNF>1</tpNF>
    <idDest>1</idDest>
    <cMunFG>3550308</cMunFG>
    <tpImp>1</tpImp>
    <tpEmis>1</tpEmis>
    <cDV>0</cDV>
    <tpAmb>2</tpAmb>
    <finNFe>1</finNFe>
    <indFinal>0</indFinal>
    <indPres>9</indPres>
    <procEmi>0</procEmi>
  </ide>
  <emit>
    <CNPJ>12345678000190</CNPJ>
    <xNome>EMPRESA DEMONSTRACAO LTDA</xNome>
    <enderEmit>
      <xLgr>Avenida Paulista</xLgr>
      <nro>1000</nro>
      <xMun>Sao Paulo</xMun>
      <UF>SP</UF>
    </enderEmit>
    <IE>123456789012</IE>
    <CRT>3</CRT>
  </emit>
  <dest>
    <CNPJ>98765432000100</CNPJ>
    <xNome>CLIENTE ATACADO S.A.</xNome>
    <enderDest>
      <xMun>Campinas</xMun>
      <UF>SP</UF>
    </enderDest>
    <indIE>1</indIE>
  </dest>
  <det nItem="1">
    <prod>
      <cProd>SKU-001</cProd>
      <cEAN>SEM GTIN</cEAN>
      <xProd>NOTEBOOK 14 POLEGADAS</xProd>
      <NCM>84713012</NCM>
      <CFOP>5101</CFOP>
      <uCom>UN</uCom>
      <qCom>2.0000</qCom>
      <vUnCom>3500.00</vUnCom>
      <vProd>7000.00</vProd>
      <indTot>1</indTot>
    </prod>
    <imposto>
      <ICMS>
        <ICMS00>
          <orig>0</orig>
          <CST>00</CST>
          <modBC>3</modBC>
          <vBC>7000.00</vBC>
          <pICMS>18.00</pICMS>
          <vICMS>1260.00</vICMS>
        </ICMS00>
      </ICMS>
      <PIS>
        <PISAliq>
          <CST>01</CST>
          <vBC>7000.00</vBC>
          <pPIS>1.65</pPIS>
          <vPIS>115.50</vPIS>
        </PISAliq>
      </PIS>
      <COFINS>
        <COFINSAliq>
          <CST>01</CST>
          <vBC>7000.00</vBC>
          <pCOFINS>7.60</pCOFINS>
          <vCOFINS>532.00</vCOFINS>
        </COFINSAliq>
      </COFINS>
    </imposto>
  </det>
  <total>
    <ICMSTot>
      <vBC>7000.00</vBC>
      <vICMS>1260.00</vICMS>
      <vProd>7000.00</vProd>
      <vNF>7000.00</vNF>
    </ICMSTot>
  </total>
</infNFe>

Esse esqueleto serve para entender a anatomia; uma nota real tem dezenas de campos condicionais. Não monte essa string na mão. Use uma biblioteca que parte dos schemas oficiais, como a nfelib, que gera classes Python a partir do XSD do leiaute 4.00:

from nfelib.nfe.bindings.v4_0.nfe import Nfe

# a nfelib expõe classes tipadas para cada nó do leiaute;
# preencher é trabalho de mapear seu pedido para os campos do schema

A vantagem de gerar a partir do XSD é estrutural: enquanto o campo existir no leiaute, ele existe no objeto; quando a SEFAZ publica uma revisão, a biblioteca é regenerada a partir do schema novo.

Validando contra o XSD

Antes de assinar, valide o XML localmente com o lxml — é barato e evita rejeições óbvias:

from lxml import etree

schema = etree.XMLSchema(etree.parse("schemes/PL_009/nfe_v4.00.xsd"))
documento = etree.parse("nota.xml")

if not schema.validate(documento):
    for erro in schema.error_log:
        print(erro)

Erros de schema detectados em milissegundos na sua máquina custam minutos (e um número de nota) no web service.

Assinando o XML com signxml

A SEFAZ exige que o nó infNFe seja assinado com XML Signature enveloped, usando RSA-SHA256 e canonicalização exclusiva, com a referência apontando para o Id da nota. Em Python, a biblioteca de referência é a signxml:

from lxml import etree
from signxml import XMLSigner, methods

arquivo = etree.parse("nota.xml")
inf_nfe = arquivo.find(".//infNFe")

with open("chave_privada.pem", "rb") as f:
    chave = f.read()
with open("certificado.pem", "rb") as f:
    certificado = f.read()

signer = XMLSigner(
    method=methods.enveloped,
    signature_algorithm="rsa-sha256",
    c14n_algorithm="http://www.w3.org/2001/10/xml-exc-c14n#",
)

assinado = signer.sign(
    inf_nfe,
    key=chave,
    cert=certificado,
    reference_uri=f"#{inf_nfe.get('Id')}",
)

Regras que evitam as rejeições mais comuns:

  • Assine o infNFe, não o documento inteiro nem o NFe.
  • Nada pode ser alterado no XML depois da assinatura — nem espaços. Qualquer edição invalida o digest.
  • O certificado precisa estar dentro da validade e pertencer ao CNPJ do emit.
  • Regerar a assinatura é sempre seguro; “consertar” o XML assinado, nunca.

Para conferir o resultado no lado de cá, valide a assinatura com XMLVerifier antes de transmitir.

Transmitindo à SEFAZ

A comunicação usa SOAP com o certificado (TLS cliente + mensagem). Os web services variam por estado e por ambiente; os dois métodos centrais da emissão são:

  • nfeAutorizacao — envia o lote (1 a 50 notas) e devolve um recibo;
  • nfeRetAutorizacao — consulta o recibo até obter o protocolo.

O fluxo com zeep tem este formato (esqueleto; consulte o WSDL do seu estado e trate timeouts como em qualquer integração HTTP — veja timeouts e retries em Python):

from zeep import Client
from zeep.wsse.signature import BinarySignature
from lxml import etree

wsdl = "https://homologacao.nfe.fazenda.sp.gov.br/ws/nfeautorizacao4.asmx?WSDL"

client = Client(wsdl, wsse=BinarySignature("certificado.pem", "chave_privada.pem"))

lote = etree.tostring(nota_assinada)
resposta = client.service.nfeAutorizacao(
    nfeDadosMsg={"enviNFe": lote},
)
print(resposta.cStat, resposta.xMotivo, resposta.infRec.nRec)

A resposta de autorização chega em cStat/xMotivo. Os códigos importantes para o seu código tratar:

cStatSignificadoO que o código faz
100AutorizadaArquiva o nfeProc e gera o DANFE
225Rejeição: falha no schemaVolta ao XSD, corrige e regera
2xx (outros)Rejeições de conteúdoCorrige o XML e reenvia a mesma nota
5xx/6xxErros de infraestruturaEstratégia de retry com espera
104Lote processadoLê o resultado por nota dentro do lote

Nunca incremente o número da nota ao rejeitar: rejeitada, a nota não consumiu numeração. O cancelamento (evento 110111) e a carta de correção também são eventos transmitidos por web service, com os mesmos cuidados de assinatura.

Consultando notas de terceiros

Para baixar notas emitidas contra o seu CNPJ (entradas), use a consulta de distribuição DFe por NSU ou chave de acesso. É o mesmo par certificado + SOAP, e o resultado alimenta pipelines de conferência — a matéria de automação de XML de NF-e mostra como extrair e validar esses arquivos em lote.

Fazer in-house ou usar uma API de emissão?

O caminho manual dá controle total, mas a manutenção é real: schemas mudam, estados divergem em detalhes de SOAP, certificados expiram. A decisão costuma ser:

CritérioEmissão própria (nfelib + signxml + zeep / PyNFe)API comercial (NFE.io, Focus NFe, eNotas, Tecnospeed)
Custo fixoSem mensalidadeMensalidade por plano/volume
Esforço inicialAlto (schemas, assinatura, WSDL)Baixo (REST + JSON)
CertificadoVocê gerencia A1O provedor assina
Manutenção de leiauteSua responsabilidadeDo provedor
Integração NFS-eSeparada, prefeitura a prefeituraMuitas prefeituras cobertas
Auditoria do fluxoTotal, no seu logDepende dos webhooks/reports

Regra prática: volume baixo ou muitas prefeituras de NFS-e → API comercial tende a pagar-se rápido. Volume alto, requisitos fortes de dados internos ou produto que já é fiscal → emissão própria, validada em homologação, com monitoramento. Muitas empresas usam as duas: API para NFS-e municipal e fluxo próprio para NF-e.

Se optar por biblioteca pronta, avalie o PyNFe, que agrupa assinatura, transmissão e consulta a vários estados. Antes de adotar qualquer biblioteca comunitária, confirme a data do último commit e a compatibilidade com o leiaute vigente — o custo de uma rejeição em produção aparece na fila da contabilidade.

Arquitetura mínima de um emissor confiável

Um emissor de NF-e em produção precisa de mais do que uma função emitir():

  1. Fila de emissão — o pedido entra, a nota é montada e transmitida de forma assíncrona; a agendamento de tarefas com APScheduler ou um worker Celery evita travar a request do usuário.
  2. Máquina de estados — montada, assinada, transmitida, autorizada/rejeitada, cancelada. Cada transição registrada com timestamp.
  3. Retry com cuidado — erros 5xx podem ser reenviados; rejeição de conteúdo exige correção. Distinga os dois no código.
  4. Log estruturado — recibo, cStat, xMotivo e chave de acesso em log pesquisável; sem log não existe diagnóstico quando a contabilidade pergunta onde a nota parou.
  5. Guarda do XML — o nfeProc autorizado é documento fiscal: arquive com backup e imutabilidade, e facilite a busca por chave de acesso.
  6. Tratamento de erros explícito — veja tratamento de erros em Python para modelar exceções de certificado, schema e web service separadamente.

Perguntas frequentes

Quanto custa emitir NF-e com Python?

O software não tem custo de licença — as bibliotecas são open source. Os custos reais são o certificado digital (anual), o tempo de desenvolvimento/homologação e, se usar, a mensalidade de uma API comercial. Não existe taxa por nota cobrada pela SEFAZ pela emissão em si.

Preciso de ERP para emitir NF-e?

Não obrigatoriamente, mas o XML precisa de dados fiscais corretos (CFOP, NCM, tributos) que normalmente vêm do sistema de gestão e da contabilidade. Python pode ser o integrador entre seu sistema e a SEFAZ, mas a responsabilidade fiscal dos campos continua sendo da empresa emitente.

O DANFE pode ser gerado com Python?

O DANFE é a representação gráfica (impressa/PDF) da NF-e autorizada. Ele pode ser gerado em Python — por exemplo com ReportLab, a partir do nfeProc — e precisa seguir o leiaute oficial, incluindo o QR Code nas notas modelo 55. Para volumes altos, provedores de emissão entregam o DANFE pronto.

Homologação aceita XML de qualquer CNPJ?

Não. O ambiente de homologação tem regras próprias (CNPJ de teste, valores fixos em alguns estados) e não exige certificado de produção em todos os cenários, mas aceita apenas o formato definido no manual. Emissões em homologação não têm valor fiscal — servem só para validar o software.

Posso emitir NF-e a partir de uma planilha Excel?

Sim, esse é um caso comum de automação: ler pedidos de um Excel com OpenPyXL, validar os campos obrigatórios, montar o XML e transmitir. A planilha é a entrada; a validação fiscal antes da montagem é o que impede rejeições em cascata.

Conclusão

Emitir NF-e com Python é viável e bem documentado: XML tipado com nfelib, assinatura com signxml, SOAP com zeep — ou uma camada única com PyNFe, ou uma API comercial quando o custo de manutenção não se justifica. O trabalho de verdade não está no código de assinatura, e sim na validação prévia, na máquina de estados da emissão e no arquivamento do que foi autorizado.

Comece pelo ambiente de homologação do seu estado, com uma nota de teste, e cresça a partir do log das rejeições. Para os fluxos adjacentes — ler notas recebidas, receber pagamentos via Pix, validar CNPJs e CEPs e gerar relatórios em Excel — os outros guias do site cobrem cada peça. E se o seu interesse em automação fiscal é profissional, veja a carreira de analista de automação com Python e as vagas Python abertas.

E
Equipe Python Dev BR

Contribuidor do Python Dev BR

Artigos relacionados