Pular para o conteúdo

Webhooks

Receba avisos no seu servidor em vez de consultar a API repetidamente.

Como configurar

  1. No painel, cadastre uma URL HTTPS e copie o segredo do webhook. A URL precisa usar a porta 443 e um domínio público: IPs, localhost, endereços de rede interna e domínios da BrazID são recusados.
  2. Responda com status 2xx em até 10 segundos. Faça o trabalho pesado depois de responder.
  3. Valide a assinatura de cada envio antes de confiar no conteúdo.

Eventos

usage.threshold_reached
O consumo chegou a 80% ou 95% da franquia do ciclo. Dispara uma vez por limite em cada ciclo.
batch.completed
Um lote terminou. O evento traz apenas contadores e datas, sem dados pessoais. Busque os resultados pelo GET /v1/cpf/batch/{id} (ou GET /v1/cnpj/batch/{id}, quando o evento traz kind: "cnpj").

Campos de usage.threshold_reached

idstring
Identificador do evento (evt_…). Os reenvios do mesmo evento trazem o mesmo id: use-o para ignorar duplicados.
typestring
Sempre usage.threshold_reached.
user_idstring
Identificador da sua conta.
thresholdnumber
Limite que foi atingido: 80 ou 95 (% da franquia do ciclo).
used_percentnumber
Percentual da franquia já consumido quando o evento foi gerado.
monthly_creditsnumber
Franquia de créditos do seu plano neste ciclo.
auto_charge_enabledboolean
true: depois da franquia a API continua respondendo e cada consulta extra é cobrada como excedente, na fatura da renovação. false: sem excedente, a API responde 402 (CREDITS_EXHAUSTED) quando os créditos acabam.
period_startstring
Início do ciclo a que o consumo se refere (ISO 8601, UTC).
period_endstring
Fim do ciclo (ISO 8601, UTC). A franquia renova nesta data.
created_atstring
Momento em que o evento foi gerado (ISO 8601, UTC).

Validando a assinatura

  • Cada envio traz o cabeçalho x-brazid-signature no formato t=<timestamp>,v1=<assinatura>.
  • A assinatura é o HMAC-SHA256, em hexadecimal, da string "<timestamp>.<corpo bruto>", calculado com o segredo do webhook.
  • Calcule o HMAC sobre o corpo exatamente como chegou, compare com v1 em tempo constante e rejeite timestamps muito antigos.

Tentativas e repetições

  • Cada evento tem um id (campo id do corpo e cabeçalho x-brazid-event-id) que não muda entre as tentativas. Use-o para ignorar duplicatas.
  • Enviamos em até 3 tentativas: na hora, 5 minutos depois e 1 hora depois, com uma pequena variação aleatória. O cabeçalho x-brazid-attempt traz o número da tentativa, e cada uma leva uma assinatura nova.
  • Conta como entregue a resposta 2xx em até 10 segundos. Repetimos quando não há resposta e nos status 5xx, 408 e 429 (respeitando o Retry-After, de até 1 hora). Os demais 4xx não são repetidos.
  • Se as 3 tentativas falharem, o evento aparece como Falhou no painel e você pode reenviá-lo à mão.
  • Quando 10 eventos seguidos falham, desativamos o webhook e avisamos por e-mail. Reative o envio no painel depois de corrigir a URL.
  • O histórico das entregas fica disponível por 30 dias.

usage.threshold_reached

1{2 "id": "evt_Xk3p9QmT2vHf",3 "type": "usage.threshold_reached",4 "user_id": "9b2f…",5 "threshold": 80,6 "used_percent": 80,7 "monthly_credits": 200,8 "auto_charge_enabled": true,9 "period_start": "2026-10-13T00:00:00.000Z",10 "period_end": "2026-11-12T00:00:00.000Z",11 "created_at": "2026-10-25T09:41:07.000Z"12}

batch.completed

1{2 "id": "evt_c7LwR1sYb0Ae",3 "type": "batch.completed",4 "batch_id": "0c1e7f3a-5b2d-4e8a-9c41-6d3f0a8b7e12",5 "total": 2,6 "found": 1,7 "not_found": 1,8 "invalid": 0,9 "errors": 0,10 "insufficient_credits": 0,11 "created_at": "2026-10-16T14:02:11.000Z",12 "completed_at": "2026-10-16T14:02:13.000Z"13}

Node.js

1import { createHmac, timingSafeEqual } from 'node:crypto';2 3export function isValidSignature(rawBody, header, secret) {4 const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));5 const expected = createHmac('sha256', secret)6 .update(`${parts.t}.${rawBody}`)7 .digest('hex');8 9 const a = Buffer.from(expected);10 const b = Buffer.from(parts.v1 ?? '');11 const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;12 13 return fresh && a.length === b.length && timingSafeEqual(a, b);14}