Emita NFS-e pelo seu sistema
Uma chamada HTTP por nota. O WAY NF monta a DPS com o perfil da empresa, assina com o certificado A1 guardado, envia à Sefin Nacional e avisa o seu sistema quando a nota sai.
Como funciona
O seu sistema continua dono do financeiro (cobranças, pagamentos, alunos, clientes). O WAY NF só transforma "esta cobrança foi paga" em NFS-e na Sefin Nacional.
- 1. Conta no WAY NF
- A empresa cria a conta no painel (login com e-mail). É por ela que você vê todas as notas, inclusive as emitidas pela API, baixa XML e cancela.
- 2. Configurar emissão (uma vez)
- No painel: dados do prestador, código do serviço, tributação e certificado A1. O sistema de vocês nunca precisa saber disso.
- 3. Criar a chave
- Em Integrações (API): uma chave para o seu sistema e, se quiser, o endereço do webhook.
- 4. Seu financeiro chama a API
- Quando uma cobrança é paga (ou quando o usuário clica em "Emitir nota"), o seu sistema manda tomador, valor, competência e descrição.
- 5. O WAY NF emite e avisa
- Em segundos a nota sai na Sefin Nacional; o webhook avisa o seu sistema com a chave de acesso, ou com o motivo da recusa.
- 6. Seu sistema mostra o resultado
- Grave a chave de acesso junto da cobrança e mostre "Nota emitida" ao usuário. Quem preferir pode acompanhar tudo só pelo painel do WAY NF.
Conectar o financeiro do seu sistema
Onde cada coisa do seu financeiro entra na API. Exemplo real: o Way School (gestão escolar) emite a nota da mensalidade quando a secretaria clica em "Emitir" numa parcela paga.
- Cobrança/parcela paga
POST /v1/requests com Idempotency-Key = id da cobrança- Responsável financeiro (nome, CPF/CNPJ)
payload.taker.name e payload.taker.tax_id- Valor pago
service_amount ("150.00")- Mês de referência / vencimento
competence (AAAA-MM-DD)- Texto da nota (ex.: mensalidade de setembro, aluno X)
payload.service.description- Situação da nota na tela do seu sistema
webhook (request.issued / rejected / cancelled) ou GET /v1/requests/{id}- Número/chave da nota guardado na cobrança
access_key do webhook- Botão "Cancelar nota"
POST /v1/requests/{id}/cancel
O que o seu sistema precisa ter: um lugar para guardar a chave (variável de ambiente no servidor), uma coluna para o id/chave da nota em cada cobrança e uma rota que recebe o webhook e confere a assinatura. Nada de certificado, XML ou regra fiscal.
Dúvidas
- Quem usa a API também entra no painel?
- Sim. Toda empresa tem conta no painel: é lá que configura a emissão, cria as chaves e vê todas as notas (as da API aparecem junto, marcadas como API). A API é só o jeito do seu sistema pedir notas sem ninguém digitar.
- Meu sistema precisa guardar certificado ou senha?
- Não. O A1 fica cifrado no WAY NF. O seu sistema guarda só a chave wnf_… (como segredo, no servidor, nunca no navegador).
- E se a chamada der timeout?
- Repita com o mesmo Idempotency-Key. O WAY NF devolve o mesmo pedido e nunca emite duas notas para a mesma chave.
- E se o webhook falhar?
- O WAY NF tenta de novo até 8 vezes. Como rede de segurança, consulte GET /v1/requests?external_id=… das notas que ainda estão em andamento.
- A nota foi recusada. E agora?
- O motivo vem em message (ex.: CPF inválido). Corrija o dado no seu sistema e envie um pedido novo com outro Idempotency-Key.
- Como testo sem emitir nota de verdade?
- Use a chave wnf_test_: emite em homologação da Sefin, sem valor fiscal. Quando estiver tudo certo, a Way libera a produção e você cria a chave wnf_live_.
- Posso emitir para várias empresas (CNPJs)?
- Cada empresa tem a sua chave. Um sistema com várias escolas/filiais guarda uma chave por empresa.
Começar em 3 passos
- No painel da empresa, em Configurar emissão, salve o perfil de emissão e instale o certificado A1.
- Em Integrações (API), crie uma chave.
wnf_test_…emite em homologação (sem valor fiscal);wnf_live_…emite de verdade e só existe depois que a Way libera a produção da empresa. - Envie as notas com
Authorization: Bearer <chave>. A chave já identifica a empresa e o ambiente.
Emitir — POST /v1/requests
Você manda só o que muda em cada nota: tomador, valor, competência e descrição. Prestador, códigos de serviço e tributação vêm do perfil da empresa.
curl -X POST https://way-nf.vercel.app/v1/requests \
-H "Authorization: Bearer $WAYNF_CHAVE" \
-H "Idempotency-Key: cobranca-2026-09-123" \
-H "Content-Type: application/json" \
-d '{
"external_id": "cobranca-2026-09-123",
"competence": "2026-09-25",
"service_amount": "150.00",
"payload": {
"taker": { "name": "Maria da Silva", "tax_id": "12345678909", "email": "maria@exemplo.com" },
"service": { "description": "Mensalidade de setembro" }
}
}'Resposta 202. Com a fila ativa a nota sai em segundos: acompanhe por consulta ou webhook.
{
"id": "cmuh71ozv0001l7044lsugi9f",
"external_id": "cobranca-2026-09-123",
"status": "queued",
"dps_number": "20003",
"status_url": "/v1/requests/cmuh71ozv0001l7044lsugi9f",
"correlation_id": "2a02a63a-0e08-4fd5-8d39-d27ba5b9a4d3"
}Node.js
const resp = await fetch("https://way-nf.vercel.app/v1/requests", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.WAYNF_CHAVE}`,
"Idempotency-Key": cobranca.id, // repetir não duplica a nota
"Content-Type": "application/json",
},
body: JSON.stringify({
external_id: cobranca.id,
competence: "2026-09-25",
service_amount: cobranca.valor.toFixed(2), // "150.00"
payload: { taker: { name: aluno.responsavel, tax_id: aluno.cpf }, service: { description: "Mensalidade de setembro" } },
}),
});
const pedido = await resp.json(); // pedido.status: "queued" | "issued" | ...Python
import os, requests
resp = requests.post(
"https://way-nf.vercel.app/v1/requests",
headers={"Authorization": f"Bearer {os.environ['WAYNF_CHAVE']}", "Idempotency-Key": cobranca_id},
json={
"external_id": cobranca_id,
"competence": "2026-09-25",
"service_amount": "150.00",
"payload": {"taker": {"name": "Maria da Silva", "tax_id": "12345678909"},
"service": {"description": "Mensalidade de setembro"}},
},
timeout=30,
)
pedido = resp.json()Consultar e baixar XML
curl https://way-nf.vercel.app/v1/requests/{id} -H "Authorization: Bearer $WAYNF_CHAVE"
curl "https://way-nf.vercel.app/v1/requests?external_id=cobranca-2026-09-123" -H "Authorization: Bearer $WAYNF_CHAVE"
curl "https://way-nf.vercel.app/v1/requests/{id}/xml?type=nfse" -H "Authorization: Bearer $WAYNF_CHAVE" -o nota.xmltype do XML: nfse (nota autorizada), dps (declaração enviada) ou cancellation (evento de cancelamento). O cabeçalho X-Content-SHA256 traz o hash para conferência.
Cancelar — POST /v1/requests/{id}/cancel
curl -X POST https://way-nf.vercel.app/v1/requests/{id}/cancel \
-H "Authorization: Bearer $WAYNF_CHAVE" \
-H "Content-Type: application/json" \
-d '{ "reason_code": 1, "reason": "Valor da mensalidade cobrado errado" }'reason_code: 1 erro na emissão, 2 serviço não prestado, 9 outros. reason: 15 a 255 caracteres. A resposta traz cancellation: "requested"; a situação vira cancelled quando a Sefin registra. Recusa (ex.: prazo vencido) mantém issued com message.
Webhooks
Cadastre um endereço https:// na integração. O WAY NF chama esse endereço quando a nota é emitida, recusada, fica incerta, é cancelada ou tem o cancelamento recusado. Responda com qualquer 2xx; sem isso, tentamos de novo com espera crescente (até 8 vezes). O mesmo aviso pode chegar mais de uma vez: use X-Way-NF-Event-ID para ignorar repetidos.
POST https://seu-sistema.com/webhooks/way-nf
X-Way-NF-Event-ID: request.issued:cmuh71ozv0001l7044lsugi9f
X-Way-NF-Timestamp: 1790354843000
X-Way-NF-Signature: 5f1c…(hex)
{
"id": "request.issued:cmuh71ozv0001l7044lsugi9f",
"schema_version": "1",
"request_id": "cmuh71ozv0001l7044lsugi9f",
"data": {
"event": "request.issued",
"external_id": "cobranca-2026-09-123",
"status": "issued",
"environment": "test",
"access_key": "33045572234483523000158000000000000926097606683075",
"dps_number": "20003",
"issued_at": "2026-09-25T16:47:23.000Z"
}
}Confira a assinatura com o segredo mostrado ao ativar o webhook:
import { createHmac, timingSafeEqual } from "node:crypto";
// corpo = texto bruto recebido (antes de JSON.parse)
function avisoValido(corpo, headers, segredo) {
const ts = headers["x-way-nf-timestamp"];
if (Math.abs(Date.now() - Number(ts)) > 5 * 60_000) return false; // evita reenvio antigo
const esperado = createHmac("sha256", segredo).update(`${ts}.${corpo}`).digest("hex");
const recebido = headers["x-way-nf-signature"] ?? "";
return recebido.length === esperado.length && timingSafeEqual(Buffer.from(recebido), Buffer.from(esperado));
}Situações da nota
accepted- Recebido, mas a empresa ainda não configurou a emissão (message explica o que falta).
queued- Na fila; sai em segundos.
processing- Sendo enviado à Sefin.
issued- Nota autorizada: access_key, dps_number e issued_at preenchidos.
rejected- Recusada pela Sefin; message traz o código e o motivo. Corrija e envie um pedido novo.
uncertain- A Sefin não respondeu com clareza. O WAY NF consulta sozinho e resolve; não reenvie.
cancelled- Cancelada na Sefin.
Erros
Corpo: { "code", "message", "correlation_id" }. Mande o correlation_id ao suporte.
- 400
- JSON ou parâmetros inválidos; chave cobre mais de uma empresa sem X-Company-Id.
- 401
- Chave ausente, errada ou revogada.
- 403
- A chave não tem essa permissão (emitir ou consultar) ou o ambiente não bate.
- 404
- Pedido não existe para esta chave.
- 409
- A mesma Idempotency-Key foi usada com outros dados.
- 422
- Dados recusados (valor, data, CPF/CNPJ, motivo de cancelamento…).
- 429
- Mais de 60 chamadas por minuto; espere o Retry-After.
Regras importantes
- Idempotência: use um
Idempotency-Keyestável por cobrança (ex.: o id dela). Repetir a chamada por timeout nunca gera nota duplicada. - external_id é único por chave: serve para achar a nota depois (
GET /v1/requests?external_id=…). - Valores em reais como texto com ponto e duas casas (
"150.00"); CPF/CNPJ só com dígitos. - Limite de 60 chamadas por minuto por chave.
- Opcionais:
X-Company-IdeX-Fiscal-Environment, só se uma chave antiga cobrir mais de uma empresa ou ambiente.