Ferramentas
Webhooks
O jeito certo de receber resultados: nós avisamos você, assinado e com retry — em vez de você gastar requisições perguntando.
Eventos
| Evento | Dispara quando |
|---|---|
analysis.completed | Análise concluída com sucesso (payload = resultado completo) |
analysis.failed | Motor não conseguiu processar (payload traz error.code e hint) |
units.low | Saldo cruzou o limiar configurado |
consent.revoked | Titular revogou consentimento via plataforma — pare de usar os dados dele |
Entrega e retries
- Esperamos
2xxem até 10 s; qualquer outra coisa agenda retry - Retries com backoff exponencial: 30 s, 2 min, 10 min, 1 h, 6 h (5 tentativas)
- Cada evento tem
event_idúnico — entregas podem se repetir; processe de forma idempotente - Falhas persistentes aparecem no painel com o corpo da resposta do seu servidor
Verifique a assinatura (sempre)
Todo webhook leva X-AOS-Signature: HMAC-SHA256 do corpo bruto com o seu webhook_secret (painel), no formato t=<timestamp>,v1=<hex>. Rejeite assinaturas inválidas ou com timestamp além de 5 minutos:
import crypto from 'node:crypto';
function verify(rawBody, header, secret) {
const { t, v1 } = Object.fromEntries(header.split(',').map(p => p.split('=')));
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
const expected = crypto.createHmac('sha256', secret)
.update(`${t}.${rawBody}`).digest('hex');
return crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected));
}import hmac, hashlib, time
def verify(raw_body: bytes, header: str, secret: str) -> bool:
parts = dict(p.split("=") for p in header.split(","))
if abs(time.time() - int(parts["t"])) > 300:
return False
expected = hmac.new(secret.encode(),
f"{parts['t']}.".encode() + raw_body,
hashlib.sha256).hexdigest()
return hmac.compare_digest(parts["v1"], expected)Payload de exemplo
json · analysis.completed
{
"event_id": "evt_9q1Xw3",
"type": "analysis.completed",
"created_at": "2026-10-01T18:22:41Z",
"data": {
"analysis_id": "ana_7Kp2mQ",
"status": "succeeded",
"overall": { "score": 71, "confidence": 0.92 },
"dimensions": [ "…resultado completo, idêntico ao GET…" ],
"metadata": { "pedido": "123" }
}
}Testando localmente
No sandbox, dispare entregas de teste sem criar análises reais:
curl
curl -X POST https://api.nutoken.com.br/v1/webhooks/test \
-H "Authorization: Bearer sk_test_..." \
-d '{"type":"analysis.completed","url":"https://seu-tunel.ngrok.app/hooks"}'