Nos últimos dias, acompanhando discussões em fóruns de desenvolvedores e repositórios no GitHub, vi um movimento que conheço muito bem de perto: programadores que cansaram de emitir nota fiscal na mão pelo portal do governo — ou que se recusaram a pagar R$ 200 de mensalidade fixa numa API tradicional para emitir meia dúzia de notas — e decidiram escrever o próprio emissor de NFS-e Nacional em Node.js e TypeScript.
Eu entendo perfeitamente essa decisão. Quando você tem um SaaS, vende assinaturas recorrentes ou presta serviço PJ e olha para o mercado tradicional de mensageria fiscal, a conta parece não fechar para quem está começando. Você pensa: "Eu já sei programar em TypeScript, já tenho meu certificado A1 .p12, a SEFIN Nacional tem uma API REST que recebe a DPS... por que eu não faço um script com uma fila em SQLite e resolvo meu problema no fim de semana?"
Criar o seu próprio emissor de NFS-e em Node.js funciona para colocar a primeira nota de pé, mas o verdadeiro custo aparece na manutenção invisível: certificados legados, diferenças de TLS entre SEFIN e ADN, o desligamento do PDF do governo (NT-008) e as quebras de schema da Reforma Tributária (NT-009).
Eu passei por cada uma dessas dores construindo a infraestrutura da Notaas e revisando implementações reais da comunidade open source. Neste artigo, quero compartilhar de forma aberta e técnica o que realmente acontece debaixo do capô de um emissor de NFS-e Nacional via API em Node.js, onde o código costuma falhar silenciosamente em produção, como você pode usar nossa biblioteca aberta @notaas/danfse-viewer se decidir manter seu próprio emissor e por que nós liberamos 50 notas fiscais gratuitas por mês por CNPJ justamente para que nenhum desenvolvedor precise escolher entre pagar mensalidade cara ou virar refém de Notas Técnicas.
Emitir a primeira nota em homologação é um projeto de fim de semana. Manter um emissor fiscal rodando sem falhar a cada Nota Técnica é um trabalho de infraestrutura contínua.
Como nasce um emissor de NFS-e Nacional próprio em Node.js
Quase todo projeto open source ou interno de emissão de NFS-e Nacional começa do mesmo jeito. O desenvolvedor não quer construir um ERP completo; ele só quer parar de preencher formulário no portal gov.br todo início de mês.
Para um SaaS ou operação de receita recorrente, a arquitetura mínima que faz sentido costuma ter quatro peças bem definidas:
- Uma tabela de vendas ou fila local (muitas vezes usando o próprio
node:sqlitenativo do Node 22/24 ou um PostgreSQL simples), separando o momento em que a venda acontece do momento em que a nota é transmitida. - Um gerador de cobranças recorrentes, onde você cadastra os contratos mensais uma única vez e roda um comando na virada do mês sem o risco de duplicar vendas se o comando for executado duas vezes.
- O motor de montagem e assinatura da DPS (Declaração de Prestação de Serviço), que transforma os dados do tomador e do serviço (
cTribNac,cNBS, valores) em XML, assina com o certificado A1, comprime em GZip + Base64 e envia para a SEFIN Nacional. - Uma rotina pós-emissão, responsável por salvar o XML autorizado, obter o PDF do DANFSe e disparar o e-mail para o cliente (usando serviços como Resend, Postmark ou AWS SES).
No papel, esse desenho é limpo e honesto. A separação entre a origem da venda (um CSV exportado da Lastlink, um webhook do Stripe ou uma cobrança do Asaas) e a fila de emissão é, inclusive, uma excelente prática de engenharia.
O problema começa quando esse código sai do teste controlado e encontra as peculiaridades da infraestrutura federal brasileira.
O primeiro tropeço: Certificado A1 (.p12), OpenSSL 3 e a cadeia da ICP-Brasil
Quem nunca trabalhou com certificado digital ICP-Brasil no Node.js costuma achar que basta ler o arquivo .pfx ou .p12 com fs.readFileSync, passar a senha para o https.Agent e sair fazendo requisição mTLS. Na prática, existem duas armadilhas logo na largada.
A primeira é o OpenSSL 3.x que vem embutido nas versões atuais do Node.js. Boa parte das certificadoras brasileiras ainda gera arquivos .p12 usando algoritmos de criptografia antigos (como RC2-40-CBC ou 3DES no envelope PKCS#12). O OpenSSL 3 desativou esses algoritmos por padrão e os moveu para o provider -legacy.
Quando você usa uma biblioteca como @nfewizard/nfse ou chama o binário do OpenSSL por baixo dos panos para extrair a chave privada e o certificado de assinatura, o código roda liso em um ambiente Linux configurado, mas quebra no Windows ou dentro de uma imagem Docker enxuta porque não encontra os módulos legados.
Já vi projetos precisarem colocar caminhos fixos do Git Mingw64 direto no código para o emissor conseguir ler o certificado no Windows:
import { existsSync } from 'node:fs';
// Contorno real que muita gente precisa fazer no Windows porque o .p12 usa cifra antiga
// e o comando `openssl pkcs12 -legacy` falha sem o path explícito dos módulos:
const GIT_OPENSSL_BIN = 'C:/Program Files/Git/mingw64/bin';
const GIT_OSSL_MODULES = 'C:/Program Files/Git/mingw64/lib/ossl-modules';
if (process.platform === 'win32' && existsSync(GIT_OSSL_MODULES)) {
process.env['OPENSSL_MODULES'] ??= GIT_OSSL_MODULES;
if (!(process.env['PATH'] ?? '').includes(GIT_OPENSSL_BIN)) {
process.env['PATH'] = `${GIT_OPENSSL_BIN};${process.env['PATH'] ?? ''}`;
}
}
A segunda armadilha é ainda mais sutil: a diferença de autoridades certificadoras entre a SEFIN Nacional e o ADN.
A SEFIN Nacional (sefin.nfse.gov.br), que é quem recebe a sua DPS e autoriza a nota, usa uma cadeia de certificados da ICP-Brasil que não vem completa na lista padrão de CAs confiáveis do Node.js (que segue o bundle da Mozilla). Se você não carregar um arquivo ca_bundle.crt próprio com as raízes e intermediárias da ICP-Brasil e do SERPRO, o Node recusa a conexão TLS dizendo que não consegue verificar a assinatura do servidor — o que na ponta aparece como o clássico erro ao gerar chave da NFS-e por falha de conexão com a API do integrador.
Aí o desenvolvedor vai lá e sobrescreve o https.globalAgent do Node com o ca_bundle.crt da SEFIN. A emissão passa a funcionar. Só que, segundos depois, quando o código tenta consultar o ADN (adn.nfse.gov.br), a chamada falha. Por quê? Porque o ADN usa certificado de uma CA pública comercial, que não está no bundle fechado que você acabou de injetar no globalAgent!
Para o sistema não quebrar no meio do fluxo, você é obrigado a gerenciar agentes HTTPS diferentes dependendo de qual servidor do governo está chamando:
import https from 'node:https';
import { readFileSync } from 'node:fs';
export function getComCertificado(
url: string,
certPath: string,
certPassword: string,
opcoes: { timeoutMs?: number; ignorarGlobalAgent?: boolean } = {}
) {
const { timeoutMs = 30000, ignorarGlobalAgent = true } = opcoes;
return new Promise<{ status: number; contentType: string; buf: Buffer }>((resolve, reject) => {
const req = https.get(
url,
{
// Precisa passar agent: false ao chamar o ADN para não herdar o globalAgent
// configurado apenas com a cadeia ICP-Brasil da SEFIN:
...(ignorarGlobalAgent ? { agent: false } : {}),
pfx: readFileSync(certPath),
passphrase: certPassword,
},
(res) => {
const chunks: Buffer[] = [];
res.on('data', (c: Buffer) => chunks.push(c));
res.on('end', () => {
resolve({
status: res.statusCode ?? 0,
contentType: (res.headers['content-type'] as string) ?? '',
buf: Buffer.concat(chunks),
});
});
}
);
req.setTimeout(timeoutMs, () => {
req.destroy(new Error(`Timeout de ${timeoutMs}ms ao conectar em ${url}`));
});
req.on('error', reject);
});
}
Esse é o tipo de detalhe que ninguém conta nos tutoriais básicos. Quem mantém um emissor próprio acaba descobrindo na marra, lendo stack trace de TLS às onze da noite.
O problema silencioso do DANFSe: por que buscar o PDF no ADN falha
Quando analisei o código de emissores open source recentes em Node.js, um ponto me chamou atenção imediatamente: como eles obtêm o PDF da nota (o DANFSe) para anexar no e-mail do cliente.
Quase todos fazem o seguinte caminho:
- Enviam a DPS para a SEFIN Nacional (
sefin.nfse.gov.br) e recebem de volta o XML autorizado com a chave de acesso de 50 dígitos. - Em seguida, fazem um
GET https://adn.nfse.gov.br/danfse/{chave}apresentando o certificado A1 via mTLS para tentar baixar o PDF pronto do Ambiente de Dados Nacional.
Olhe como isso costuma ser implementado na rotina de envio de e-mail:
// Exemplo típico em emissores locais: tentar baixar o PDF direto do ADN logo após emitir
export async function baixarDanfseDoAdn(ambiente: 1 | 2, chave: string): Promise<Buffer> {
const host = ambiente === 1
? 'https://adn.nfse.gov.br'
: 'https://adn.producaorestrita.nfse.gov.br';
const res = await getComCertificado(`${host}/danfse/${chave}`, config.certPath, config.certPassword);
if (res.status !== 200) {
throw new Error(`DANFSe HTTP ${res.status}`);
}
return res.buf;
}
// Na hora de mandar o e-mail para o cliente:
let pdf: Buffer | null = null;
try {
pdf = await baixarDanfseDoAdn(config.ambiente, chave);
} catch (err) {
console.warn(`DANFSe indisponível (${err}), enviando e-mail sem anexo`);
}
Reparou no try / catch ali embaixo com o aviso "enviando e-mail sem anexo"? Esse bloco não está ali por acaso. Ele existe porque buscar o PDF no ADN logo depois de emitir na SEFIN falha com frequência.
Existem três motivos técnicos para isso acontecer:
- Atraso de sincronização entre SEFIN e ADN: Você autoriza a nota na SEFIN Nacional, mas pede o PDF para o ADN. Como a replicação interna entre os dois ambientes nem sempre acontece no mesmo segundo, o seu script pede o PDF 300ms depois da emissão e recebe um
HTTP 404ou502. O resultado prático é péssimo para a imagem do seu SaaS: o cliente recebe o e-mail de confirmação da nota fiscal, mas sem o PDF da nota anexado. - Descontinuação da API de visualização do ADN pela NT-008: Como já detalhei no nosso artigo sobre o fim do visualizador do ADN e como gerar o DANFSe localmente, a Nota Técnica nº 008 (NT-008) do Comitê Gestor da NFS-e determinou o desligamento da API antiga de geração de PDF do ADN, exigindo que os próprios sistemas emissores gerem o documento auxiliar no novo layout DANFSe v2.0.
- Dependência desnecessária de rede e certificado: Você já tem o XML autorizado e assinado salvo na sua máquina no momento em que a SEFIN responde. Fazer mais uma conexão mTLS externa apenas para transformar esse XML em PDF adiciona latência e um ponto único de falha que você não controla.
Como resolver isso de vez usando o @notaas/danfse-viewer (Open Source MIT)
Quando vimos que o desligamento do visualizador do ADN e a chegada do DANFSe v2.0 na NT-008 iam quebrar dezenas de sistemas em Node.js e TypeScript pelo Brasil, nós tomamos uma decisão aqui na Notaas: além de rodar o nosso motor de PDF em produção na nossa API, nós abrimos o código do parser e do gerador de layout como um pacote 100% Open Source (licença MIT).
- Pacote no NPM:
@notaas/danfse-viewer - Repositório no GitHub: github.com/wedigibrasil/notaas-danfse
- Visualizador online gratuito para testes: danfse.notaas.com.br
Se você mantém um emissor próprio em Node.js — seja por estudo, seja para rodar internamente na sua empresa — você não precisa mais fazer GET no ADN e correr o risco de mandar e-mail sem anexo.
O @notaas/danfse-viewer recebe a string do XML autorizado que a SEFIN acabou de te devolver e monta todo o documento DANFSe v2.0 localmente, sem fazer uma única requisição de rede:
- Base do IBGE e ativos embutidos: Os 5.570 municípios brasileiros já estão mapeados dentro do pacote para traduzir o código IBGE (
cLocEmi,cLocIncid) em nome da cidade e UF, o logotipo oficial da NFS-e Nacional já está em Base64 e o QR Code de verificação é gerado em memória. - Fidelidade visual à NT-008 v1.02: Todas as regras chatas da especificação técnica já estão prontas — desde a tipografia exata (
Arialnos rótulos eMicrosoft Sans Serifnos valores em preto sólido#000) até a supressão automática de blocos vazios (quando não há Intermediário ou quando certos quadros tributários não se aplicam, o espaço é redistribuído automaticamente para a Descrição do Serviço). - Cálculo e exibição de IBS e CBS: O parser já trata os grupos novos da Reforma Tributária, diferenciando corretamente um campo que não veio no XML (
-) de uma alíquota que veio preenchida com zero (0,00%).
Veja como fica simples substituir a chamada ao ADN por geração local no seu código TypeScript:
import { DanfseXmlParser, DanfseHtmlBuilder } from '@notaas/danfse-viewer';
import puppeteer from 'puppeteer-core';
/**
* Gera o PDF do DANFSe v2.0 (NT-008) localmente a partir do XML autorizado,
* eliminando a chamada frágil a https://adn.nfse.gov.br/danfse/{chave}.
*/
export async function gerarPdfDanfseLocal(xmlAutorizado: string): Promise<Buffer> {
// 1. Extrai os dados estruturados do XML segundo o mapeamento da NT-008
const parser = new DanfseXmlParser();
const data = await parser.parse(xmlAutorizado);
// 2. Gera o HTML completo formatado para folha A4 retrato
const builder = new DanfseHtmlBuilder();
const html = builder.build(data);
// 3. Converte para PDF A4 mantendo cores de fundo e bordas exatas
const browser = await puppeteer.launch({
executablePath: process.env.CHROME_PATH || '/usr/bin/chromium-browser',
args: ['--no-sandbox', '--disable-setuid-sandbox'],
});
try {
const page = await browser.newPage();
await page.setViewport({ width: 1240, height: 1754 });
await page.setContent(html, { waitUntil: 'domcontentloaded' });
const pdf = await page.pdf({
format: 'A4',
printBackground: true,
margin: { top: '0mm', right: '0mm', bottom: '0mm', left: '0mm' },
});
return Buffer.from(pdf);
} finally {
await browser.close();
}
}
Com menos de 30 linhas, o seu emissor para de depender do humor do ADN e garante que 100% dos e-mails enviados aos seus clientes saiam com o PDF anexado.
A dor das Notas Técnicas: NT-009, "leiaute" da DPS e patches em bibliotecas
Quem acompanha a documentação oficial do governo sabe que até o vocabulário tem suas particularidades — os manuais escrevem "leiaute" em vez de layout, e a cada poucos meses surge uma Nota Técnica nova alterando a estrutura do XML.
A Nota Técnica nº 009 (NT-009), preparada para acomodar as mudanças da Reforma Tributária (IBS e CBS) em produção, é o exemplo clássico de como um emissor próprio exige vigilância constante.
Quando uma Nota Técnica muda a posição ou a obrigatoriedade de uma tag dentro do grupo <infDPS> — como a tag <finNFSe> (finalidade de emissão da NFS-e) ou a estrutura de <IBSCBS> — acontece um efeito cascata para quem usa bibliotecas da comunidade em Node.js:
- A biblioteca que você instalou via NPM (como
@nfewizard/nfse) muitas vezes ainda não foi atualizada pelo mantenedor para refletir a Nota Técnica nova. - O validador XSD da SEFIN Nacional é estrito com a ordem sequencial dos elementos XML. Se
<finNFSe>não estiver exatamente depois de<cLocEmi>e antes de<prest>, ou se você tentar emitir em produção (tpAmb = 1) com o schema novo antes da data de virada oficial do governo, a nota é rejeitada na hora. - Para não ficar com o faturamento parado esperando um PR ser aceito na biblioteca de terceiros, você acaba tendo que usar
patch-packagee manter arquivos.patchdentro do seu repositório alterando na mão o JavaScript minificado dentro denode_modules.
⚡ O que muda na prática com a NT-009 e a Reforma Tributária na DPS:
- 1. Ordem estrita em
<infDPS>: Inclusão e posicionamento exato de<finNFSe>antes dos blocos de atores (prest,toma) e serviço.- 2. Janela de transição entre ambientes: Necessidade de validar a data de entrada em vigor na produção (
tpAmb = 1) versus produção restrita (tpAmb = 2) considerando o fuso de Brasília (America/Sao_Paulo).- 3. Validação cruzada da Tríade Fiscal: Os códigos de tributação nacional (
cTribNac), a Nomenclatura Brasileira de Serviços (cNBS) e a classificação tributária do IBS/CBS (cClassTrib) precisam conversar entre si para não disparar rejeições como o Erro E637 na emissão com IBS e CBS.
Eu sempre digo que o maior risco de um emissor self-hosted não é o código que você escreveu hoje; é a Nota Técnica que o governo vai publicar daqui a três meses, bem na semana em que você estiver no meio de um lançamento importante do seu produto.
Recorrência em SaaS: fila local no terminal vs. automação real por webhook
Outro ponto que vale analisar com calma é a operação do dia a dia de quem tem um SaaS ou vende serviços recorrentes.
Em um emissor de linha de comando (CLI), o fluxo geralmente funciona assim:
- No fim ou no início do mês, você baixa a planilha de vendas da plataforma de pagamento (Lastlink, Hotmart, Kiwify, Stripe) ou roda um comando para gerar as parcelas dos contratos ativos no banco SQLite.
- Você roda uma simulação no terminal para ver se os dados batem.
- Depois, executa o comando de fila com uma flag
--realpara que o script processe as notas uma a uma na SEFIN e dispare os e-mails.
Para quem emite 10 notas por mês de contratos B2B fechados manualmente, rodar um comando no terminal uma vez por mês é tranquilo. Mas quando você tem um SaaS com vendas entrando automaticamente pelo checkout, esse fluxo manual começa a pesar por três motivos:
1. O cliente quer a nota no momento do pagamento (ou do fim da garantia)
Empresas que assinam um SaaS B2B com cartão corporativo ou PIX precisam que a nota fiscal chegue automaticamente no e-mail do financeiro sem que o fundador precise lembrar de abrir o terminal no notebook onde está salvo o arquivo .p12.
2. Concorrência e controle do número sequencial da DPS (nDPS)
Em um script local com SQLite, você impede que duas emissões rodem ao mesmo tempo criando um arquivo fila.lock na pasta do projeto e incrementando o nDPS localmente. Mas se você colocar seu sistema na nuvem (em containers, serverless ou múltiplos workers) ou se o seu contador emitir uma nota avulsa direto pelo portal web da NFS-e Nacional usando a mesma série, o seu contador local de nDPS fica dessincronizado e a fila trava com erro de numeração duplicada.
3. Conciliação quando a SEFIN dá timeout no meio do envio
Todo mundo que já integrou com a SEFIN Nacional sabe o que acontece quando a conexão cai depois que o pacote HTTP já saiu: você fica sem saber se a DPS foi processada ou não. Em um emissor manual, você precisa rodar um comando de conciliação e consultar a chave ou o número da DPS antes de devolver a venda para a fila.
Na API da Notaas, nós desenhamos a fila assíncrona justamente para tirar esse peso das costas do desenvolvedor:
{
"externalId": "venda_lastlink_2026_10_88412",
"environment": "production",
"competenceDate": "2026-10-24",
"service": {
"description": "Assinatura Mensal SaaS - Outubro/2026",
"amount": 197.00,
"cTribNac": "010701",
"cNBS": "115021000",
"cClassTrib": "000001"
},
"customer": {
"document": "27865757000102",
"name": "CLIENTE SAAS EXEMPLO LTDA",
"email": "financeiro@clienteexemplo.com.br",
"address": {
"zipCode": "04538133",
"street": "Avenida Brigadeiro Faria Lima",
"number": "3477",
"neighborhood": "Itaim Bibi",
"cityCode": "3550308",
"state": "SP"
}
}
}
Você só envia esse JSON simples para POST https://api.notaas.com.br/api/v1/nfse passando o ID da venda ou do contrato no campo externalId. Se o webhook do seu gateway disparar duas ou três vezes seguidas, a nossa trava de idempotência garante que a nota nunca será duplicada. E assim que a SEFIN autoriza, nós geramos o DANFSe v2.0 local (usando o mesmo motor do @notaas/danfse-viewer) e entregamos tudo pronto no seu webhook assinado com HMAC SHA-256.
E quando o município não está no Padrão Nacional?
Existe ainda uma barreira geográfica importante. Um emissor construído direto sobre a API da SEFIN Nacional só funciona se a prefeitura do prestador estiver 100% conveniada e ativa no Padrão Nacional (SNNFSE) para recepção direta de DPS.
Se a sua empresa estiver em um município que usa sistema próprio fora do barramento nacional — ou se você decidir transformar a funcionalidade fiscal do seu sistema em um produto para clientes de outras cidades (como São Paulo capital com particularidades municipais, Barueri com seu layout RPS de texto posicional, municípios com Tinus, Atende.Net, ISSNet, Betha ou Ginfes) — o código feito apenas para a SEFIN Nacional não serve.
Foi por isso que construímos uma camada única de abstração com cobertura para mais de 4.780 municípios brasileiros. O seu SaaS manda sempre o mesmo JSON limpo, independentemente de a prefeitura usar o SNNFSE federal ou um webservice municipal legado.
A conta real para quem tem SaaS: Self-Hosted vs. 50 Notas Grátis na Notaas
Como desenvolvedor, eu adoro quando o autor de um projeto open source é transparente sobre os trade-offs da própria ferramenta: em um emissor próprio, você não paga por nota para uma API, mas assume a responsabilidade pelo certificado, pela instalação, pelas diferenças entre sistemas operacionais e por acompanhar cada Nota Técnica do governo.
A pergunta que fizemos quando fundamos a Notaas foi simples: por que o desenvolvedor de um SaaS ou micro-SaaS deveria ser obrigado a escolher entre pagar R$ 200 por mês numa API antiga ou perder finais de semana dando manutenção em XML e certificado A1?
Para acabar com essa escolha injusta, nós criamos o modelo de 50 notas fiscais gratuitas por mês por CNPJ em produção real (sem exigir cartão de crédito e sem prazo de expiração):
| O que você precisa resolver | Mantendo Emissor Próprio em Node.js | Usando a API da Notaas (Plano Gratuito) |
|---|---|---|
| Custo mensal para emitir até 50 notas/mês | R$ 0 (mas exige seu tempo de engenharia e manutenção) | R$ 0,00 / mês em produção real (sem cartão) |
Certificado A1 (.p12) e OpenSSL Legacy |
Você configura na máquina/VPS e resolve conflitos de CAs da SEFIN/ADN | Upload único no cofre seguro (ou via API); nós cuidamos do mTLS |
| Geração do PDF (DANFSe v2.0 NT-008) | Precisa integrar o @notaas/danfse-viewer + Puppeteer para não depender do ADN |
PDF gerado localmente e entregue pronto via URL e Webhook |
| Atualizações da NT-008 e NT-009 (IBS/CBS) | Você precisa atualizar código e aplicar patches sempre que o schema mudar | Atualizado automaticamente pela nossa equipe de engenharia fiscal |
| Idempotência e Fila 24/7 | Banco SQLite local ou infraestrutura própria de filas para manter no ar | Fila gerenciada na nuvem com externalId anti-duplicidade e retries |
| Múltiplos CNPJs (SaaS B2B / Contabilidades) | Difícil de escalar (1 arquivo .env e 1 certificado local por empresa) |
Org API (POST /api/v1/org/projects) com 50 notas grátis para cada CNPJ |
Se o seu SaaS emite 10, 25 ou 50 notas fiscais por mês, usar a API da Notaas custa exatamente os mesmos R$ 0,00 de rodar um script próprio na sua máquina — só que você integra via HTTP REST em 15 minutos, não precisa instalar OpenSSL nem guardar certificado .p12 no servidor da aplicação, e nunca mais perde o sono porque o governo soltou uma Nota Técnica nova em dezembro.
E quando o seu SaaS crescer e passar de 50 vendas no mês, a sua operação já estará validada e faturando, pagando apenas centavos por nota excedente sem precisar reescrever uma única linha de código.
Exemplo prático: automatizando a emissão no seu SaaS com menos de 30 linhas
Para mostrar na prática a diferença de complexidade, veja como fica um endpoint no seu backend (em Node.js, Next.js ou qualquer framework TypeScript) recebendo a confirmação de pagamento do seu gateway (Stripe, Asaas, Lastlink, Kiwify) e enfileirando a nota fiscal na Notaas:
interface VendaConfirmada {
idVenda: string;
valor: number;
descricaoServico: string;
cliente: {
cpfCnpj: string;
nome: string;
email: string;
cep: string;
logradouro: string;
numero: string;
bairro: string;
codigoIbge: string;
uf: string;
};
}
export async function emitirNfsePosVenda(venda: VendaConfirmada) {
const resposta = await fetch('https://api.notaas.com.br/api/v1/nfse', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.NOTAAS_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
// Se esta função rodar duas vezes para a mesma venda, a nota NÃO duplica:
externalId: venda.idVenda,
environment: 'production',
service: {
description: venda.descricaoServico,
amount: venda.valor,
cTribNac: '010701',
cNBS: '115021000',
},
customer: {
document: venda.cliente.cpfCnpj,
name: venda.cliente.nome,
email: venda.cliente.email,
address: {
zipCode: venda.cliente.cep,
street: venda.cliente.logradouro,
number: venda.cliente.numero,
neighborhood: venda.cliente.bairro,
cityCode: venda.cliente.codigoIbge,
state: venda.cliente.uf,
},
},
}),
});
if (!resposta.ok) {
throw new Error(`Falha ao enfileirar NFS-e: ${await resposta.text()}`);
}
return resposta.json();
}
Sem certificado .p12 exposto no disco do container, sem ca_bundle.crt, sem patch-package em node_modules e sem risco de mandar e-mail sem PDF porque o ADN piscou. Se você curte desenvolver usando assistentes de IA como Cursor ou Lovable, vale dar uma olhada também no nosso guia prático de Vibe Coding Fiscal para integrar notas em Next.js.
Perguntas comuns de quem está decidindo entre código próprio e API
Posso continuar usando meu emissor próprio em Node.js e usar só o gerador de DANFSe da Notaas?
Sim! O pacote @notaas/danfse-viewer é 100% open source sob licença MIT. Você pode instalá-lo via npm install @notaas/danfse-viewer em qualquer projeto comercial ou pessoal para transformar o XML autorizado pela SEFIN Nacional no HTML/PDF oficial do DANFSe v2.0 (NT-008) sem depender do endpoint /danfse/{chave} do ADN.
Os códigos de serviço (cTribNac e cNBS) ainda precisam ser confirmados com o contador?
Sempre. Nem um emissor open source nem uma API de transmissão substituem o enquadramento contábil da sua empresa. O ideal é validar uma única vez com o seu contador qual é o par correto de cTribNac (item da LC 116/03 no padrão de 6 dígitos), cNBS (9 dígitos) e cClassTrib para a atividade do seu SaaS, e parametrizar esses valores fixos no seu payload de emissão (veja nosso guia sobre como emitir com IBS e CBS no schema DPS v1.01).
As 50 notas gratuitas por mês valem para ambiente de produção real?
Sim. Não é apenas ambiente de homologação (sandbox). Todo CNPJ cadastrado na Notaas pode emitir até 50 notas fiscais por mês com validade jurídica em produção real (incluindo NFS-e, NF-e e NFC-e), todos os meses, sem precisar cadastrar cartão de crédito.
E se eu tiver vários CNPJs ou clientes no meu SaaS?
Você pode usar a nossa Org API para múltiplos CNPJs (POST /api/v1/org/projects) para criar um projeto isolado para cada CNPJ via código, fazer o upload do certificado A1 daquele cliente e emitir com tokens independentes — aproveitando a franquia de 50 notas gratuitas mensais em cada CNPJ.
Conclusão: qual caminho escolher?
Se você gosta de escovar bits, quer aprender na prática como funciona mTLS com certificado ICP-Brasil e tem tempo para acompanhar as Notas Técnicas da SEFIN Nacional, construir ou contribuir com um emissor open source em Node.js é uma baita escola — e o nosso pacote @notaas/danfse-viewer no GitHub está lá aberto justamente para ajudar você a gerar o DANFSe v2.0 local sem depender do ADN.
Agora, se o que você quer mesmo é faturar os clientes do seu SaaS no piloto automático, sem pagar mensalidade fixa e sem ter que consertar código fiscal todo fim de ano quando entra uma Nota Técnica nova, teste a Notaas:

