Webhooks
Receba avisos no seu servidor em vez de consultar a API repetidamente.
Como configurar
- 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.
- Responda com status 2xx em até 10 segundos. Faça o trabalho pesado depois de responder.
- 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}(ouGET /v1/cnpj/batch/{id}, quando o evento trazkind: "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-signatureno formatot=<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}
1import hmac2import hashlib3import time4 5 6def is_valid_signature(raw_body: bytes, header: str, secret: str) -> bool:7 parts = dict(p.split("=", 1) for p in header.split(","))8 signed = parts["t"].encode() + b"." + raw_body9 expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()10 11 fresh = abs(time.time() - int(parts["t"])) < 30012 return fresh and hmac.compare_digest(expected, parts.get("v1", ""))