Documentação Técnica para Desenvolvedores — DFe Inbound
- Produto: DFe Inbound — Recepção de NF-e e CT-e
- Público: Desenvolvedores que integram sistemas com a API nfe.io
- Versão da API: v2
Índice
- Introdução
- Guia de Primeiros Passos
- Conceitos Fundamentais
- Autenticação
- URL Base e Ambientes
- Ativando o Serviço de Inbound
- Endpoints de NF-e
- Endpoints de CT-e
- Consulta com OData (CT-e)
- Webhooks — Recebendo Notificações
- Referência de Tipos e Enums
- Tratamento de Erros
- Exemplos de Integração
- Perguntas Frequentes (FAQ)
1. Introdução
O DFe Inbound da nfe.io é um serviço que monitora automaticamente a SEFAZ e captura todos os Documentos Fiscais Eletrônicos (NF-e e CT-e) emitidos por terceiros que têm como destinatário o CNPJ da sua empresa.
O que você ganha com este serviço?
- Recepção automática: Sem precisar acessar o portal da SEFAZ manualmente.
- XML armazenado: Os XMLs ficam disponíveis para download via API a qualquer momento.
- Notificações em tempo real: Receba webhooks assim que um documento chegar.
- Dados estruturados: Consulte metadados (emitente, valor, data) sem precisar parsear XML.
- Histórico completo: Acesso a documentos recebidos nos últimos 90 dias (ou desde o NSU configurado).
Visão Geral da Integração
SEFAZ (Ambiente Nacional)
│
│ (Polling automático via NSU)
▼
nfe.io DFe Inbound
│
├──▶ Armazena XML no cloud storage
├──▶ Salva metadados (emitente, valor, data...)
└──▶ Dispara Webhook ──▶ SEU SISTEMA
│
├── Consulta metadados via API
└── Baixa XML via API
2. Guia de Primeiros Passos
Se você está integrando pela primeira vez, siga esta sequência em ordem. Não pule etapas.
Etapa 1 — Obter credenciais
No painel nfe.io, crie uma API Key em Configurações → Chaves de API. Guarde-a com segurança — ela só é exibida uma vez.
Etapa 2 — Localizar seu company_id
Em Empresas, clique na empresa que deseja integrar e copie o company_id exibido na URL ou nos detalhes da empresa.
Etapa 3 — Ativar o inbound
Faça uma requisição para ativar o monitoramento do CNPJ:
curl -X POST \
"https://api.nfe.io/v2/companies/SEU_COMPANY_ID/inbound/productinvoices" \
-H "Authorization: ApiKey SUA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"StartFromNsu": 0,
"StartFromDate": "2024-01-01T00:00:00Z",
"EnvironmentSEFAZ": "Production",
"WebhookVersion": 2
}'
Resposta esperada: { "status": "Active", ... }
Etapa 4 — Configurar seu endpoint de webhook
No painel nfe.io, vá em Webhooks e cadastre a URL do seu servidor que vai receber as notificações.
Mínimo necessário no seu endpoint:
# Python (Flask)
@app.route('/webhook', methods=['POST'])
def webhook():
data = request.json
access_key = data['accessKey']
# Processar...
return '', 200 # OBRIGATÓRIO retornar 200
Etapa 5 — Verificar que documentos chegam
Após ativar, aguarde entre 15 minutos e 4 horas. Consulte documentos recebidos:
curl "https://api.nfe.io/v2/companies/SEU_COMPANY_ID/inbound/productinvoices/CHAVE_44_DIGITOS" \
-H "Authorization: ApiKey SUA_API_KEY"
Etapa 6 — Migrar para produção
Quando seus testes estiverem OK com "EnvironmentSEFAZ": "Test", recrie a configuração com "Production":
# 1. Desativar o inbound de homologação
curl -X DELETE \
"https://api.nfe.io/v2/companies/SEU_COMPANY_ID/inbound/productinvoices" \
-H "Authorization: ApiKey SUA_API_KEY"
# 2. Ativar com ambiente de produção
curl -X POST \
"https://api.nfe.io/v2/companies/SEU_COMPANY_ID/inbound/productinvoices" \
-H "Authorization: ApiKey SUA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"StartFromNsu": 0,
"StartFromDate": "2024-01-01T00:00:00Z",
"EnvironmentSEFAZ": "Production",
"WebhookVersion": 2
}'
Importante: Documentos de ambiente
Test(homologação SEFAZ) eProductionsão separados. Nunca misture os dois na mesma configuração.
3. Conceitos Fundamentais
NSU — Número Sequencial Único
O NSU é um número mantido pela SEFAZ que cresce a cada documento recebido pelo seu CNPJ. Pense nele como um "contador de correspondências" da SEFAZ para a sua empresa.
Exemplo:
- NSU 1000: NF-e de fornecedor A, valor R$ 500
- NSU 1001: NF-e de fornecedor B, valor R$ 1.200
- NSU 1002: Evento de cancelamento da NF-e do fornecedor A
Nosso serviço consulta a SEFAZ periodicamente e baixa todos os documentos a partir do último NSU processado.
Chave de Acesso (access_key)
É um código de 44 dígitos que identifica unicamente uma NF-e ou CT-e. Exemplo:
35240112345678000195550010000012341234567890
│ │ │ │ │ │
│ │ │ │ │ └─── Dígito verificador
│ │ │ │ └────── Número NF-e
│ │ │ └─────────── Série
│ │ └────────────────────────── CNPJ emitente
│ └──────────────────────────── Mês/Ano emissão (AAAAMM)
└─────────────────────────────── UF (35 = SP)
Para eventos (cancelamento, ciência, etc.), a chave tem 55 dígitos.
Identificando o tipo de documento pela chave
A posição 20-21 da chave de acesso (0-indexed) indica o modelo do documento:
| Posição 20-21 | Tipo | Descrição |
|---|---|---|
55 | NF-e | Nota Fiscal Eletrônica |
57 | CT-e | Conhecimento de Transporte Eletrônico |
65 | NFC-e | Nota Fiscal de Consumidor |
67 | CT-eOS | CT-e para Outros Serviços |
def get_document_type(access_key: str) -> str:
model = access_key[20:22] # posições 20 e 21
types = {"55": "NF-e", "57": "CT-e", "65": "NFC-e", "67": "CT-eOS"}
return types.get(model, "Desconhecido")
Tipos de Registro retornados pela API
| MetadataResourceType | Significado |
|---|---|
productInvoice | NF-e completa (XML disponível) |
productInvoiceEvent | Evento de NF-e (cancelamento, ciência, etc.) |
productInvoiceSummary | Resumo de NF-e (apenas metadados básicos) |
productInvoiceEventSummary | Resumo de evento |
transportationInvoice | CT-e completo |
transportationInvoiceEvent | Evento de CT-e |
4. Autenticação
Todas as requisições exigem autenticação. Suportamos dois métodos:
Método 1 — API Key (recomendado para integração server-to-server)
Passe a chave de API no header Authorization:
Authorization: ApiKey SUA_API_KEY_AQUI
Onde obter a API Key: Painel nfe.io → Configurações → Chaves de API.
Método 2 — Bearer Token (JWT)
Use um token JWT gerado via https://id.nfe.io:
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
Exemplo de Requisição com Autenticação
curl -X GET \
"https://api.nfe.io/v2/companies/comp_123/inbound/productinvoices" \
-H "Authorization: ApiKey sk_live_abc123..."
5. URL Base e Ambientes
| Ambiente | URL Base |
|---|---|
| Produção | https://api.nfe.io |
| Sandbox (testes) | https://api.sandbox.nfe.io |
Estrutura da URL:
{url_base}/v2/companies/{company_id}/inbound/...
O company_id é o identificador da sua empresa na plataforma nfe.io (encontrado no Painel → Empresas).
6. Ativando o Serviço de Inbound
Antes de receber documentos, você precisa ativar o serviço de inbound para cada CNPJ que deseja monitorar.
Ativar Inbound de NF-e
POST /v2/companies/{company_id}/inbound/productinvoices
Authorization: ApiKey {api_key}
Content-Type: application/json
{
"StartFromNsu": 0,
"StartFromDate": "2024-01-01T00:00:00Z",
"EnvironmentSEFAZ": "Production",
"AutomaticManifesting": {
"MinutesToWaitAwarenessOperation": 60
},
"WebhookVersion": 2
}
Campos do corpo:
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
StartFromNsu | long | Não | NSU inicial. 0 = busca desde o início. Use um NSU específico para sincronizar com histórico |
StartFromDate | DateTime | Não | Data inicial para busca. Padrão: 90 dias atrás |
EnvironmentSEFAZ | string | Sim | "Production" (produção SEFAZ) ou "Test" (homologação SEFAZ) |
EventType | string | Não | Filtro de tipos de evento (códigos separados por vírgula). null = todos |
Schema | int | Não | 0=NF-e + Eventos, 1=Somente NF-e, 2=Somente Eventos |
AutomaticManifesting.MinutesToWaitAwarenessOperation | int | Não | Minutos aguardados antes de auto-manifestar ciência. Mínimo: 5 |
WebhookVersion | int | Não | Versão do formato de webhook. Use 2 (mais recente) |
Resposta de sucesso (200):
{
"companyId": "comp_123",
"environmentSEFAZ": "Production",
"startFromNsu": 0,
"startFromDate": "2024-01-01T00:00:00Z",
"status": "Active",
"createdOn": "2024-03-15T10:30:00Z"
}
Ativar Inbound de CT-e
POST /v2/companies/{company_id}/inbound/transportationinvoices
Authorization: ApiKey {api_key}
Content-Type: application/json
{
"StartFromNsu": 0,
"StartFromDate": "2024-01-01T00:00:00Z",
"EnvironmentSEFAZ": "Production"
}
Verificar Configuração Atual
GET /v2/companies/{company_id}/inbound/productinvoices
Authorization: ApiKey {api_key}
Desativar Inbound
DELETE /v2/companies/{company_id}/inbound/productinvoices
Authorization: ApiKey {api_key}
7. Endpoints de NF-e
Buscar Metadados de uma NF-e
Retorna os dados estruturados de uma NF-e pela sua chave de acesso.
GET /v2/companies/{company_id}/inbound/productinvoices/{access_key}
Authorization: ApiKey {api_key}
Parâmetros de URL:
company_id: ID da sua empresa na nfe.ioaccess_key: Chave de acesso de 44 dígitos da NF-e
Resposta de sucesso (200):
{
"accessKey": "35240112345678000195550010000012341234567890",
"createdOn": "2024-03-15T14:22:10Z",
"nsu": "21825",
"nsuParent": null,
"nfeNumber": "1234",
"nfeSerialNumber": "1",
"issuedOn": "2024-03-15T10:00:00Z",
"type": "productInvoice",
"description": "Autorizado o uso da NF-e",
"totalInvoiceAmount": "1500.00",
"operationType": "Incoming",
"issuer": {
"federalTaxNumber": "12345678000195",
"name": "Fornecedor LTDA"
},
"buyer": {
"federalTaxNumber": "98765432000100",
"name": "Minha Empresa S.A."
},
"company": {
"id": "comp_123",
"federalTaxNumber": "98765432000100"
},
"links": {
"xml": "https://storage.nfe.io/temp/xml/...",
"pdf": "https://storage.nfe.io/temp/pdf/..."
}
}
Descrição dos campos:
| Campo | Tipo | Descrição |
|---|---|---|
accessKey | string | Chave de acesso 44 dígitos |
createdOn | DateTime | Quando o documento entrou no sistema |
nsu | string | Número Sequencial Único na SEFAZ |
nfeNumber | string | Número da NF-e |
nfeSerialNumber | string | Série da NF-e |
issuedOn | DateTime | Data de emissão |
type | string | productInvoice, productInvoiceEvent, productInvoiceSummary |
description | string | Status da NF-e (ex: "Autorizado o uso da NF-e") |
totalInvoiceAmount | string | Valor total em reais |
operationType | string | Incoming (destinatário) ou Outgoing (emitente) |
issuer | object | Dados do emitente (quem emitiu a NF-e) |
buyer | object | Dados do destinatário (comprador) |
links.xml | string | URL temporária para download do XML (expira em 1 hora) |
links.pdf | string | URL temporária para download do PDF/DANFE |
Baixar XML de uma NF-e
GET /v2/companies/{company_id}/inbound/productinvoices/{access_key}/xml
Authorization: ApiKey {api_key}
# Retorna o arquivo XML diretamente (Content-Type: application/xml)
Alternativamente, use a URL temporária retornada em links.xml para download direto do storage (sem precisar passar pela API). Esse link expira em 1 hora.
Baixar PDF (DANFE) de uma NF-e
GET /v2/companies/{company_id}/inbound/productinvoices/{access_key}/pdf
Authorization: ApiKey {api_key}
# Retorna o arquivo PDF (Content-Type: application/pdf)
Buscar Dados de um Evento de NF-e
Eventos são ações sobre a NF-e: cancelamento, ciência, confirmação de operação, etc.
GET /v2/companies/{company_id}/inbound/productinvoices/{access_key}/events/{event_key}
Authorization: ApiKey {api_key}
O event_key tem 55 dígitos (chave de acesso da NF-e + código do evento + sequência).
Registrar Manifestação
A manifestação é o processo pelo qual o destinatário comunica à SEFAZ que tem conhecimento da NF-e. É obrigatória para NF-es de entrada.
POST /v2/companies/{company_id}/inbound/{access_key}/manifest?tpEvent=210210
Authorization: ApiKey {api_key}
Tipos de manifestação (tpEvent):
| Código | Tipo | Descrição |
|---|---|---|
210210 | Ciência da Operação | "Estou ciente desta NF-e" (não confirma recebimento físico) |
210200 | Confirmação da Operação | "Recebi a mercadoria conforme NF-e" |
210220 | Desconhecimento da Operação | "Não reconheço esta operação" |
210240 | Operação não Realizada | "A operação não foi concluída" |
Resposta (200):
"Manifestação registrada com sucesso"
Manifestação Automática: Se você configurou
AutomaticManifesting.MinutesToWaitAwarenessOperation, o sistema registrará "Ciência da Operação" automaticamente após esse intervalo. Você não precisa chamar este endpoint para ciência se tiver auto-manifestação ativa.
Reprocessar Webhook de uma NF-e
Use quando o webhook não foi entregue ou precisa ser reenviado.
POST /v2/companies/{company_id}/inbound/productinvoices/{access_key}/processwebhook
Authorization: ApiKey {api_key}
# Ou por NSU:
POST /v2/companies/{company_id}/inbound/productinvoices/{nsu}/processwebhook
8. Endpoints de CT-e
Ativar e Configurar CT-e
Veja a seção Ativando o Serviço de Inbound — o processo é idêntico ao NF-e mas no endpoint /transportationinvoices.
Buscar Metadados de um CT-e
GET /v2/companies/{company_id}/inbound/{access_key}
Authorization: ApiKey {api_key}
Resposta (200):
{
"id": "abc123",
"accessKey": "35240198765432000100570010000009871234567890",
"createdOn": "2024-03-15T14:22:10Z",
"nsu": 10042,
"type": "transportationInvoice",
"description": "Autorizado o uso do CT-e",
"company": {
"id": "comp_123",
"federalTaxNumber": "98765432000100"
},
"issuer": {
"federalTaxNumber": "11111111000100",
"name": "Transportadora ABC LTDA"
},
"taker": {
"federalTaxNumber": "98765432000100",
"name": "Minha Empresa S.A."
},
"totalAmount": 350.00,
"issuedOn": "2024-03-15T10:00:00Z",
"xmlUrl": "https://storage.nfe.io/temp/cte/..."
}
Campos específicos do CT-e:
| Campo | Tipo | Descrição |
|---|---|---|
issuer | object | A transportadora que emitiu o CT-e |
taker | object | Tomador do serviço de transporte |
totalAmount | decimal | Valor do serviço de transporte |
issuedOn | DateTime | Data de emissão do CT-e |
xmlUrl | string | URL temporária para download do XML (expira em 1 hora) |
Baixar XML de um CT-e
GET /v2/companies/{company_id}/inbound/{access_key}/xml
Authorization: ApiKey {api_key}
# Retorna o arquivo XML (Content-Type: application/xml)
Reprocessar Webhooks de CT-e
POST /v2/companies/{company_id}/inbound/transportationinvoices/reprocess/webhook
Authorization: ApiKey {api_key}
Content-Type: application/json
{
"Key": "35240198765432000100570010000009871234567890",
"Date": "2024-03-15"
}
Reprocessar NSUs Específicos
Útil quando um NSU falhou no processamento:
PUT /v2/companies/{company_id}/inbound/transportationinvoices/reprocess/item
Authorization: ApiKey {api_key}
Content-Type: application/json
[10042, 10043, 10044]
Consolidação de Batch
Consolida batches em um intervalo de NSU:
PUT /v2/companies/{company_id}/inbound/transportationinvoices/batch/consolidation
Authorization: ApiKey {api_key}
Content-Type: application/json
{
"StartNSU": 10000,
"EndNSU": 10500
}
9. Consulta com OData (CT-e)
O endpoint OData permite consultas avançadas com filtros, ordenação e paginação.
Buscar Lista de CT-es
GET /v2/companies/{company_id}/inbound/odata/TransportationInvoices
Authorization: ApiKey {api_key}
Parâmetros OData Suportados
| Parâmetro | Descrição | Exemplo |
|---|---|---|
$filter | Filtra registros | issuedOn ge 2024-01-01 |
$top | Máximo de registros por página (máx: 1000) | $top=100 |
$skip | Ignora N registros (offset) | $skip=200 |
$skiptoken | Token de paginação (mais eficiente que skip) | $skiptoken=abc123 |
$orderby | Ordenação | $orderby=issuedOn desc |
$select | Seleciona campos específicos | $select=accessKey,issuedOn |
Exemplos de Filtros
CT-es do mês de janeiro de 2024:
GET /v2/companies/{company_id}/inbound/odata/TransportationInvoices
?$filter=issuedOn ge 2024-01-01T00:00:00Z and issuedOn lt 2024-02-01T00:00:00Z
&$top=100
&$orderby=issuedOn desc
CT-es com NSU maior que 5000:
GET /v2/companies/{company_id}/inbound/odata/TransportationInvoices
?$filter=nsu gt 5000
&$top=50
Paginação com $skiptoken
Para listas grandes, use $skiptoken em vez de $skip. O token é retornado na resposta quando há mais páginas:
{
"@odata.context": "...",
"@odata.nextLink": "https://api.nfe.io/v2/companies/comp_123/inbound/odata/TransportationInvoices?$skiptoken=abc123",
"value": [
{ "accessKey": "...", "issuedOn": "..." },
{ "accessKey": "...", "issuedOn": "..." }
]
}
Na próxima requisição, use a URL de @odata.nextLink diretamente.
Buscar Eventos de CT-e
GET /v2/companies/{company_id}/inbound/odata/TransportationInvoiceEvents
?$filter=receiptOn ge 2024-01-01T00:00:00Z
&$top=100
O campo de filtro para eventos é receiptOn (data de recebimento) em vez de issuedOn.
10. Webhooks — Recebendo Notificações
O webhook permite que seu sistema seja notificado automaticamente quando um novo documento é recebido.
Como Configurar
- Acesse o Painel nfe.io -> Empresas -> {sua empresa} -> Webhooks
- Cadastre a URL do seu endpoint
- Ative o inbound (como descrito na seção 6) com
WebhookVersion: 2
Formato do Payload (Webhook v2)
Quando um documento é recebido, fazemos um POST para sua URL com o seguinte corpo:
{
"event": "nfe.received",
"companyId": "comp_123",
"accessKey": "35240112345678000195550010000012341234567890",
"nsu": "21825",
"type": "productInvoice",
"issuedOn": "2024-03-15T10:00:00Z",
"totalAmount": 1500.00,
"issuer": {
"federalTaxNumber": "12345678000195",
"name": "Fornecedor LTDA"
},
"buyer": {
"federalTaxNumber": "98765432000100",
"name": "Minha Empresa S.A."
}
}
Tipos de evento (event):
| Evento | Quando ocorre |
|---|---|
nfe.received | Nova NF-e recebida |
nfe.event.received | Evento de NF-e recebido (cancelamento, etc.) |
cte.received | Novo CT-e recebido |
cte.event.received | Evento de CT-e recebido |
Respondendo ao Webhook
Seu endpoint deve retornar HTTP 200 em até 30 segundos. Se não retornar 200, tentaremos novamente com backoff exponencial por até 24 horas.
# Exemplo em Python (Flask)
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route('/webhooks/nfeio', methods=['POST'])
def receive_nfe():
payload = request.json
if payload['event'] == 'nfe.received':
access_key = payload['accessKey']
# Processar o documento...
save_to_database(payload)
return jsonify({"status": "ok"}), 200
Validando a Autenticidade do Webhook
Para garantir que a requisição realmente veio da nfe.io e não de terceiros mal-intencionados, valide o header de assinatura:
import hmac
import hashlib
def validate_webhook(payload_bytes: bytes, received_signature: str, secret: str) -> bool:
"""
Valida que o webhook veio da nfe.io usando HMAC-SHA256.
- payload_bytes: corpo da requisição em bytes (sem decodificar)
- received_signature: valor do header X-NFe-Signature
- secret: seu webhook secret configurado no painel nfe.io
"""
expected = hmac.new(
secret.encode('utf-8'),
payload_bytes,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, received_signature)
# No seu endpoint:
@app.route('/webhooks/nfeio', methods=['POST'])
def receive_webhook():
signature = request.headers.get('X-NFe-Signature', '')
if not validate_webhook(request.data, signature, 'seu_secret_aqui'):
return 'Assinatura inválida', 401
# Processar...
return '', 200
Configure o Webhook Secret no painel nfe.io → Webhooks. Sem ele, qualquer pessoa que descobrir sua URL pode simular notificações.
Reprocessar um Webhook
Se precisar receber novamente o webhook de um documento específico:
POST /v2/companies/{company_id}/inbound/productinvoices/{access_key}/processwebhook
Authorization: ApiKey {api_key}
11. Referência de Tipos e Enums
EnvironmentSEFAZ
| Valor | Descrição |
|---|---|
"Test" | Ambiente de homologação SEFAZ — documentos sem validade fiscal |
"Production" | Ambiente de produção SEFAZ — documentos com validade fiscal real |
OperationType
| Valor | Descrição |
|---|---|
"Incoming" | Você é o destinatário (recebeu o documento) |
"Outgoing" | Você é o emitente (emitiu o documento) |
MetadataResourceType
| Valor | Descrição |
|---|---|
"productInvoice" | NF-e completa |
"productInvoiceEvent" | Evento de NF-e |
"productInvoiceSummary" | Resumo de NF-e (sem XML completo) |
"productInvoiceEventSummary" | Resumo de evento de NF-e |
"transportationInvoice" | CT-e |
"transportationInvoiceEvent" | Evento de CT-e |
EntityStatus
| Valor | Descrição |
|---|---|
"Active" | Configuração ativa — sistema monitorando |
"Inactive" | Configuração inativa — sem monitoramento |
12. Tratamento de Erros
Formato do Erro
Quando ocorre um erro, a API retorna um JSON no seguinte formato:
{
"errors": [
{
"code": 404,
"message": "Document not found for the given access key."
}
]
}
Códigos HTTP
| HTTP | Significa | O que fazer |
|---|---|---|
200 | Sucesso | Processe a resposta normalmente |
204 | Sucesso sem conteúdo | Operação realizada, sem corpo de resposta |
400 | Requisição inválida | Verifique os parâmetros enviados |
401 | Não autorizado | Verifique sua API Key ou token |
403 | Proibido | Sua conta não tem permissão para este recurso |
404 | Não encontrado | O documento/empresa não existe |
409 | Conflito (duplicado) | Recurso já existe |
422 | Entidade não processável | Dados válidos mas não processáveis (ex: NF-e cancelada) |
503 | Serviço indisponível | SEFAZ temporariamente fora. Tente novamente em alguns minutos |
504 | Timeout | A operação demorou muito. Tente novamente |
500 | Erro interno | Entre em contato com o suporte |
Headers de Rate Limiting
Todas as respostas incluem headers indicando o status do rate limit:
| Header | Descrição |
|---|---|
X-RateLimit-Limit | Total de requisições permitidas por janela |
X-RateLimit-Remaining | Requisições restantes na janela atual |
X-RateLimit-Reset | Timestamp Unix quando a janela reinicia |
Quando o limite é excedido, a API retorna 429 Too Many Requests. Aguarde até X-RateLimit-Reset antes de tentar novamente.
Estratégia de Retry
Para erros 429, 503 e 504, recomendamos retry com backoff exponencial:
import time
import requests
def get_with_retry(url, headers, max_retries=3):
for attempt in range(max_retries):
response = requests.get(url, headers=headers)
if response.status_code == 429:
reset_time = int(response.headers.get('X-RateLimit-Reset', time.time() + 60))
wait_time = max(reset_time - time.time(), 1)
time.sleep(wait_time)
continue
if response.status_code in (503, 504):
wait_time = (2 ** attempt) * 1 # 1s, 2s, 4s
time.sleep(wait_time)
continue
return response
raise Exception("Max retries exceeded")
13. Exemplos de Integração
Exemplo 1 — Buscar NF-e por Chave de Acesso (C#)
using System.Net.Http;
using System.Text.Json;
public class NfeIoClient
{
private readonly HttpClient _http;
private readonly string _companyId;
private readonly string _baseUrl = "https://api.nfe.io";
public NfeIoClient(string apiKey, string companyId)
{
_companyId = companyId;
_http = new HttpClient();
_http.DefaultRequestHeaders.Add("Authorization", $"ApiKey {apiKey}");
}
public async Task<NfeMetadata> GetNfeAsync(string accessKey)
{
var url = $"{_baseUrl}/v2/companies/{_companyId}/inbound/productinvoices/{accessKey}";
var response = await _http.GetAsync(url);
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<NfeMetadata>(content);
}
public async Task<Stream> GetNfeXmlAsync(string accessKey)
{
var url = $"{_baseUrl}/v2/companies/{_companyId}/inbound/productinvoices/{accessKey}/xml";
return await _http.GetStreamAsync(url);
}
}
Exemplo 2 — Consumir Webhook e Baixar XML (Node.js)
const express = require('express');
const axios = require('axios');
const crypto = require('crypto');
const app = express();
app.use(express.raw({ type: 'application/json' })); // raw para validar assinatura
const API_KEY = 'sk_live_sua_chave_aqui';
const COMPANY_ID = 'comp_123';
const WEBHOOK_SECRET = 'seu_webhook_secret';
const BASE_URL = 'https://api.nfe.io';
function validateSignature(payload, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}
app.post('/webhooks/nfeio', async (req, res) => {
// Validar assinatura
const signature = req.headers['x-nfe-signature'] || '';
if (!validateSignature(req.body, signature, WEBHOOK_SECRET)) {
return res.status(401).send('Assinatura inválida');
}
const payload = JSON.parse(req.body);
const { event, accessKey, type, issuer, totalAmount } = payload;
console.log(`Novo documento: ${accessKey} - ${event}`);
try {
const xmlResponse = await axios.get(
`${BASE_URL}/v2/companies/${COMPANY_ID}/inbound/productinvoices/${accessKey}/xml`,
{ headers: { 'Authorization': `ApiKey ${API_KEY}` }, responseType: 'text' }
);
await saveDocumentToDatabase({ accessKey, type, issuer, totalAmount, xml: xmlResponse.data });
res.status(200).json({ status: 'ok' });
} catch (error) {
console.error('Erro:', error);
res.status(500).json({ error: error.message });
}
});
app.listen(3000);
Exemplo 3 — Listar CT-es do Último Mês com Paginação (Python)
import requests
from datetime import datetime, timedelta
API_KEY = "sk_live_sua_chave"
COMPANY_ID = "comp_123"
BASE_URL = "https://api.nfe.io"
def get_all_ctes_last_month():
"""Busca todos os CT-es do último mês usando paginação OData."""
today = datetime.utcnow()
last_month = today - timedelta(days=30)
start_date = last_month.strftime("%Y-%m-%dT%H:%M:%SZ")
end_date = today.strftime("%Y-%m-%dT%H:%M:%SZ")
url = (
f"{BASE_URL}/v2/companies/{COMPANY_ID}/inbound/odata/TransportationInvoices"
f"?$filter=issuedOn ge {start_date} and issuedOn lt {end_date}"
f"&$top=100"
f"&$orderby=issuedOn desc"
)
headers = {"Authorization": f"ApiKey {API_KEY}"}
all_ctes = []
while url:
response = requests.get(url, headers=headers)
response.raise_for_status()
data = response.json()
all_ctes.extend(data.get("value", []))
# OData retorna próxima página via @odata.nextLink
url = data.get("@odata.nextLink")
print(f"Buscados {len(all_ctes)} CT-es até agora...")
return all_ctes
ctes = get_all_ctes_last_month()
print(f"Total: {len(ctes)} CT-es")
for cte in ctes[:5]:
print(f" - {cte['accessKey']} | Emitido em: {cte['issuedOn']}")
Exemplo 4 — Ativar Inbound e Aguardar Primeiro Documento (PHP)
<?php
class NfeIoInbound
{
private string $apiKey;
private string $companyId;
private string $baseUrl = 'https://api.nfe.io';
public function __construct(string $apiKey, string $companyId)
{
$this->apiKey = $apiKey;
$this->companyId = $companyId;
}
private function makeRequest(string $method, string $path, ?array $body = null): array
{
$url = "{$this->baseUrl}/v2/companies/{$this->companyId}/{$path}";
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_HTTPHEADER => [
"Authorization: ApiKey {$this->apiKey}",
"Content-Type: application/json"
],
]);
if ($body) {
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
}
$response = curl_exec($ch);
$statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($statusCode >= 400) {
throw new RuntimeException("API Error {$statusCode}: {$response}");
}
return json_decode($response, true);
}
public function enableNfeInbound(string $startDate = null): array
{
return $this->makeRequest('POST', 'inbound/productinvoices', [
'StartFromNsu' => 0,
'StartFromDate' => $startDate ?? date('Y-m-d\TH:i:s\Z', strtotime('-90 days')),
'EnvironmentSEFAZ' => 'Production',
'WebhookVersion' => 2,
]);
}
public function getNfeMetadata(string $accessKey): array
{
return $this->makeRequest('GET', "inbound/productinvoices/{$accessKey}");
}
}
// Uso:
$client = new NfeIoInbound('sk_live_abc123', 'comp_123');
$config = $client->enableNfeInbound('2024-01-01T00:00:00Z');
echo "Inbound ativado! Status: " . $config['status'];
14. Perguntas Frequentes (FAQ)
Quanto tempo leva para receber um documento após a emissão?
Em condições normais, entre 30 minutos e 4 horas após a autorização da SEFAZ. O nosso sistema faz polling periódico na SEFAZ; o intervalo depende da configuração, da sincronização entre a SEFAZ de origem e o Ambiente Nacional e da disponibilidade da SEFAZ.
O que é um "resumo" (productInvoiceSummary)?
Quando o XML completo ainda não está disponível no Ambiente Nacional, a SEFAZ retorna apenas um resumo com os dados básicos da NF-e. Eventualmente, o documento completo chega e é atualizado automaticamente.
Posso buscar documentos históricos?
Sim. A SEFAZ mantém documentos disponíveis por até 90 dias para consulta via NSU. Configure StartFromDate ou StartFromNsu ao ativar o inbound para sincronizar o histórico.
A URL do XML expira?
Sim. As URLs retornadas em links.xml e links.pdf expiram em 1 hora. Para download, use a URL logo após obtê-la, ou chame o endpoint /xml a qualquer momento para obter uma nova URL.
O que acontece se meu webhook estiver fora do ar?
O sistema tentará reenviar o webhook com backoff exponencial por até 24 horas. Após esse período, você pode usar o endpoint de reprocessamento para solicitar novo envio.
Preciso manifestar todas as NF-es?
Pela legislação brasileira, o destinatário deve manifestar ciência de operação para NF-es de entrada (que você está comprando). Configure AutomaticManifesting para que o sistema faça isso automaticamente.
Como testar sem afetar o ambiente de produção?
Configure com EnvironmentSEFAZ: "Test" para usar o ambiente de homologação SEFAZ. Documentos emitidos em homologação não têm validade fiscal. Ao terminar os testes, delete a configuração e recrie com "Production".
Qual o limite de requisições?
Por padrão, o limite é de 60 requisições por minuto por empresa. Os headers X-RateLimit-Limit, X-RateLimit-Remaining e X-RateLimit-Reset informam o status atual. Para limites maiores, entre em contato com o suporte nfe.io.
Como saber se o inbound está atrasado?
Consulte GET /productinvoices e compare OperationDetail_CurrentNsu com OperationDetail_MaxNsu. Se a diferença for grande e OperationDetail_ExecutedOn for antigo, pode haver um problema. Verifique se a configuração está Active e se o certificado digital não expirou.
Documentação mantida pela equipe de desenvolvimento nfe.io. Para reportar problemas ou solicitar funcionalidades, acesse o suporte em nfe.io/suporte.