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
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.
# 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"
}'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.
Consulta de NF-e
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
| Campo | Tipo | Descrição |
|---|---|---|
| chave | string (obrig.) | Chave de acesso de 44 dígitos. |
| referencia | string | Seu ID interno. Volta nos webhooks e no histórico. |
curl -X POST https://api.vigiaxml.com/v1/consulta \
-H "X-Api-Key: $VIGIA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"chaveAcesso": "35240300000000000000550010000001231123456785"
}'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_idResposta 200
{
"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"
}Monitoramento contínuo
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
| Campo | Tipo | Descrição |
|---|---|---|
| chave | string (obrig.) | Chave de acesso de 44 dígitos. |
| referencia | string | Seu ID interno (operação, contrato). Recomendado para idempotência. |
| callback_url | string (https) | Endpoint que recebe os webhooks. Pode ser configurado globalmente no painel. |
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"
}'// 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
{
"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"
}Lê o estado atual de uma chave em monitoramento.
Listagem incremental — útil pra reconciliar caso o webhook tenha ficado off.
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
| Evento | Quando dispara |
|---|---|
| nota.consultada | Após cada consulta à SEFAZ (opcional via configuração). |
| nota.status_alterado | Quando o status muda em uma chave monitorada (ex: AUTORIZADA → CANCELADA). |
| monitoramento.iniciado | Confirmação de que a vigilância começou. |
| monitoramento.encerrado | Janela de 30 dias completou ou você cancelou via API/painel. |
Payload de exemplo
{
"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 eventox-vigia-signature— HMAC-SHA256 hexx-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.
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.
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)
# 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"Códigos e como reagir
Toda resposta de erro segue o mesmo formato JSON, com request_id para referência de suporte:
{
"error": {
"code": "invalid_chave",
"message": "Chave de acesso com dígito verificador inválido",
"fields": ["chave"],
"request_id": "req_01HF3Z6PV6N8KQ4S2YB7CGT5XR"
}
}| Code | HTTP | Significado | O que fazer |
|---|---|---|---|
| invalid_request | 400 | Payload malformado, campo faltando ou tipo errado. | Confira o body — o detalhe vem em error.fields. |
| invalid_chave | 400 | Chave de acesso com 44 dígitos mas dígito verificador errado. | Reveja a chave (provável erro de digitação ou conversão). |
| unauthenticated | 401 | Token ausente, mal formatado ou revogado. | Cheque o header X-Api-Key e regenere a chave no painel se preciso. |
| forbidden | 403 | Token válido, mas sem permissão para esse recurso. | Confira o escopo do token. Tokens de leitura não podem criar monitoramento. |
| not_found | 404 | Recurso (monitoramento, ID) não existe nesta carteira. | Verifique o ID ou se o recurso está em outra conta. |
| rate_limited | 429 | Excedeu o rate limit do plano. | Aguarde Retry-After segundos. Use endpoint em lote para volumes maiores. |
| sefaz_unavailable | 502 | SEFAZ devolveu erro ou timeout. Não cacheamos: a falha é repassada. | Repetir após o tempo sugerido em Retry-After. |
| internal_error | 500 | Falha interna do VigiaXML. | Repetir com backoff. Persistindo, escreva para suporte com o request_id. |
Limites por carteira
Limites são por carteira (não por token). Cabeçalhos em toda resposta mostram o estado atual:
| Header | Significado |
|---|---|
| x-ratelimit-limit | Cota total no janela atual. |
| x-ratelimit-remaining | Quantas chamadas ainda cabem antes de 429. |
| x-ratelimit-reset | Epoch (segundos) quando a janela renova. |
| retry-after | Segundos 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.