VigiaXML
Desenvolvedores

Documentação da API VigiaXML

REST, JSON, autenticação via header X-Api-Key. Webhooks assinados com HMAC-SHA256, idempotência por chave + referência e retry com backoff. Exemplos cURL e TypeScript prontos para colar.

Base URL: https://api.vigiaxml.com · OpenAPI publicado, tipos TypeScript em @vigiaxml/types

Quickstart

Da chave de acesso ao primeiro 200

Em 3 passos: pegue a sua API key no painel, exporte-a como variável de ambiente e dispare uma consulta. Em 1-3s você tem o status oficial da SEFAZ — funciona para NF-e, CT-e e MDF-e.

quickstart.shbash
# 1. Exporte a API key gerada em https://app.vigiaxml.com/settings/tokens
export VIGIA_API_KEY="vk_..."

# 2. Consulte uma chave (a API detecta o modelo pelas posições 21-22)
curl -X POST https://api.vigiaxml.com/v1/consulta \
  -H "X-Api-Key: $VIGIA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "chaveAcesso": "35240300000000000000550010000001231123456785" }'

# 3. Coloque a mesma chave em monitoramento contínuo
curl -X POST https://api.vigiaxml.com/v1/monitoramento \
  -H "X-Api-Key: $VIGIA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "chaveAcesso": "35240300000000000000550010000001231123456785",
    "callbackUrl": "https://erp.example.com/webhooks/vigia"
  }'
Autenticação

API key por carteira (header X-Api-Key)

Cada API key pertence a uma carteira (tenant). Gere e revogue no painel em Configurações → Tokens. Chaves são prefixadas com vk_ e mostradas uma única vez na criação.

Envie no header X-Api-Key: <sua-chave>. A API responde 401 se a chave for inválida ou tiver sido revogada.

Boa prática: nunca commit API keys. Use variáveis de ambiente, secret manager (Azure Key Vault, AWS Secrets Manager) ou um cofre nativo do seu CI/CD.
Endpoint

Consulta de NF-e

POST/v1/consultaconsome 1 unidade de consulta avulsa

Faz uma chamada síncrona à webservice da SEFAZ e devolve o status atual da nota. Não cacheado — a resposta reflete o estado oficial no instante da chamada.

Body

CampoTipoDescrição
chavestring (obrig.)Chave de acesso de 44 dígitos.
referenciastringSeu ID interno. Volta nos webhooks e no histórico.
consulta.shbash
curl -X POST https://api.vigiaxml.com/v1/consulta \
  -H "X-Api-Key: $VIGIA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "chaveAcesso": "35240300000000000000550010000001231123456785"
  }'
consulta.tstypescript
import type { ConsultaResponse } from "@vigiaxml/types";

const res = await fetch("https://api.vigiaxml.com/v1/consulta", {
  method: "POST",
  headers: {
    "X-Api-Key": process.env.VIGIA_API_KEY!,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    chaveAcesso: "35240300000000000000550010000001231123456785",
  }),
});

if (!res.ok) throw new Error(`Consulta falhou: ${res.status}`);
const data = (await res.json()) as ConsultaResponse;
// data.status: "autorizada" | "cancelada" | "denegada" | "inexistente" | "outro"
// data.c_stat: "100" | "101" | "217" | ...   (código oficial SEFAZ)
// data.x_motivo: mensagem oficial SEFAZ
// data.uf: "SP" | "MG" | ...
// data.modelo: "NF-e" | "CT-e" | "MDF-e" | "CT-e OS"
// data.duration_ms, data.queried_at, data.consulta_id

Resposta 200

response.jsonjson
{
  "consulta_id": "c5520f10-0511-425d-81b0-ffc75a81ca25",
  "chave_acesso": "35240300000000000000550010000001231123456785",
  "modelo": "NF-e",
  "modelo_codigo": 55,
  "uf": "SP",
  "status": "autorizada",
  "c_stat": "100",
  "x_motivo": "Autorizado o uso da NF-e",
  "situacao": "cStat=100 (consultar Anexo II do MOC NF-e)",
  "duration_ms": 1247,
  "queried_at": "2026-05-03T20:31:11.523Z"
}
Endpoint

Monitoramento contínuo

POST/v1/monitoramentoconsome 1 unidade de XML monitorado (vale por 30 dias)

Coloca a NF-e em vigilância automática durante a janela de risco. A cadência é horária nas primeiras 24h após a emissão (onde concentra a maioria dos cancelamentos) e diária até o 30º dia. Cada mudança de status dispara um webhook.

Body

CampoTipoDescrição
chavestring (obrig.)Chave de acesso de 44 dígitos.
referenciastringSeu ID interno (operação, contrato). Recomendado para idempotência.
callback_urlstring (https)Endpoint que recebe os webhooks. Pode ser configurado globalmente no painel.
monitoramento.shbash
curl -X POST https://api.vigiaxml.com/v1/monitoramento \
  -H "X-Api-Key: $VIGIA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "chaveAcesso": "35240300000000000000550010000001231123456785",
    "referencia": "OP-2026-04-12345",
    "callbackUrl": "https://erp.example.com/webhooks/vigia"
  }'
monitoramento.tstypescript
// Adicionar um documento (NF-e/CT-e/MDF-e) ao monitoramento
const res = await fetch("https://api.vigiaxml.com/v1/monitoramento", {
  method: "POST",
  headers: {
    "X-Api-Key": process.env.VIGIA_API_KEY!,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    chaveAcesso: "35240300000000000000550010000001231123456785",
    referencia: "OP-2026-04-12345",   // seu ID interno (opcional)
    callbackUrl: "https://erp.example.com/webhooks/vigia",
  }),
});
const { id, expira_em } = await res.json();

Resposta 201

response.jsonjson
{
  "id": "5e1d8a64-2c4f-4a3a-9ae8-5a0fbb2c9871",
  "chave": "35240300000000000000550010000001231123456785",
  "referencia": "OP-2026-04-12345",
  "callback_url": "https://erp.example.com/webhooks/vigia",
  "status_atual": "AUTORIZADA",
  "criado_em": "2026-05-03T20:33:02Z",
  "expira_em": "2026-06-02T20:33:02Z",
  "proxima_consulta_em": "2026-05-03T21:33:02Z"
}
GET/v1/monitoramento/:chave

Lê o estado atual de uma chave em monitoramento.

GET/v1/mudancas?since=…

Listagem incremental — útil pra reconciliar caso o webhook tenha ficado off.

Webhooks

Eventos e payload

Sempre que algo muda, postamos um JSON na sua callback_url. A entrega é assinada com HMAC-SHA256 (veja Verificação HMAC), idempotente por id_entrega e retentada com backoff por até 24h se você não devolver 2xx em 5 segundos.

Eventos

EventoQuando dispara
nota.consultadaApós cada consulta à SEFAZ (opcional via configuração).
nota.status_alteradoQuando o status muda em uma chave monitorada (ex: AUTORIZADA → CANCELADA).
monitoramento.iniciadoConfirmação de que a vigilância começou.
monitoramento.encerradoJanela de 30 dias completou ou você cancelou via API/painel.

Payload de exemplo

webhook.jsonjson
{
  "evento": "nota.status_alterado",
  "id_entrega": "wd_01HF3Z6PV6N8KQ4S2YB7CGT5XR",
  "tentativa": 1,
  "chave": "35240300000000000000550010000001231123456785",
  "referencia": "OP-2026-04-12345",
  "status_anterior": "AUTORIZADA",
  "status_atual": "CANCELADA",
  "detectado_em": "2026-04-12T15:42:08Z",
  "snapshot": {
    "c_stat": "101",
    "x_motivo": "Cancelamento de NF-e homologado",
    "valor_total": 14759.00,
    "cnpj_emitente": "00000000000000",
    "consultada_em": "2026-04-12T15:42:06Z"
  }
}

Headers

  • x-vigia-event— nome do evento
  • x-vigia-signature— HMAC-SHA256 hex
  • x-vigia-delivery-id— id desta tentativa

Idempotência

Use id_entrega ou (chave + referencia + evento) como chave de dedup.

Retry

Não-2xx ou timeout (5s) → backoff exponencial por até 24h. Sucesso encerra. Falha definitiva fica visível no painel.

Segurança

Verificação HMAC-SHA256

Cada webhook traz a assinatura no header x-vigia-signature. O algoritmo é HMAC-SHA256 do corpo cru (bytes recebidos, antes de qualquer parsing) com a sua chave secreta de webhook, codificado em hex.

  • Use o body raw — um JSON.stringify(JSON.parse(body)) muda espaços e quebra a comparação.
  • Compare em tempo constante (timingSafeEqual) para evitar timing attacks.
  • Rotação: gere uma chave nova no painel; aceitamos a antiga por mais 48h para você atualizar deploys sem downtime.
verify-webhook.tstypescript
import crypto from "node:crypto";
import express from "express";

const app = express();

// Importante: precisamos do RAW body pra calcular o digest. Não use express.json()
// antes desse handler — perde o byte exato e a assinatura nunca bate.
app.post(
  "/webhooks/vigia",
  express.raw({ type: "application/json" }),
  (req, res) => {
    const signature = req.header("x-vigia-signature") ?? "";
    const expected = crypto
      .createHmac("sha256", process.env.VIGIA_WEBHOOK_SECRET!)
      .update(req.body) // Buffer raw, não JSON.stringify
      .digest("hex");

    // timingSafeEqual evita timing attack
    const ok =
      signature.length === expected.length &&
      crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));

    if (!ok) return res.status(401).send("invalid signature");

    const payload = JSON.parse(req.body.toString("utf-8"));
    // Idempotência: dedup por (chave, referencia, evento) ou por id_entrega.
    // Se já processou, devolva 2xx mesmo assim — a entrega para de retentar.

    res.status(200).send("ok");
  },
);

Verificação manual (debug)

verify.shbash
# Verificar manualmente uma payload recebida (debug)
SIGNATURE=$(echo -n "$RAW_BODY" | openssl dgst -sha256 -hmac "$VIGIA_WEBHOOK_SECRET" -hex | awk '{print $2}')
echo "esperado: $SIGNATURE"
echo "recebido: $X_VIGIA_SIGNATURE_HEADER"
Erros

Códigos e como reagir

Toda resposta de erro segue o mesmo formato JSON, com request_id para referência de suporte:

error.jsonjson
{
  "error": {
    "code": "invalid_chave",
    "message": "Chave de acesso com dígito verificador inválido",
    "fields": ["chave"],
    "request_id": "req_01HF3Z6PV6N8KQ4S2YB7CGT5XR"
  }
}
CodeHTTPSignificadoO que fazer
invalid_request400Payload malformado, campo faltando ou tipo errado.Confira o body — o detalhe vem em error.fields.
invalid_chave400Chave de acesso com 44 dígitos mas dígito verificador errado.Reveja a chave (provável erro de digitação ou conversão).
unauthenticated401Token ausente, mal formatado ou revogado.Cheque o header X-Api-Key e regenere a chave no painel se preciso.
forbidden403Token válido, mas sem permissão para esse recurso.Confira o escopo do token. Tokens de leitura não podem criar monitoramento.
not_found404Recurso (monitoramento, ID) não existe nesta carteira.Verifique o ID ou se o recurso está em outra conta.
rate_limited429Excedeu o rate limit do plano.Aguarde Retry-After segundos. Use endpoint em lote para volumes maiores.
sefaz_unavailable502SEFAZ devolveu erro ou timeout. Não cacheamos: a falha é repassada.Repetir após o tempo sugerido em Retry-After.
internal_error500Falha interna do VigiaXML.Repetir com backoff. Persistindo, escreva para suporte com o request_id.
Rate limits

Limites por carteira

Limites são por carteira (não por token). Cabeçalhos em toda resposta mostram o estado atual:

HeaderSignificado
x-ratelimit-limitCota total no janela atual.
x-ratelimit-remainingQuantas chamadas ainda cabem antes de 429.
x-ratelimit-resetEpoch (segundos) quando a janela renova.
retry-afterSegundos para esperar quando recebeu 429 ou 502 sefaz_unavailable.

Limites padrão por plano: 120 req/min em consulta avulsa e 600 chaves/minem monitoramento por carteira. Volume maior é negociado — fale com vendas.

Pronto para o primeiro deploy?

Cadastre-se, gere um token de teste e dispare a primeira consulta em menos de 5 minutos. Dúvida específica? Vendas responde em horário comercial.