Selo de estado:
Preview(teto atual do produto) · Curso ACD-420 — Analista de IA: relatórios, alertas, anomalias e previsões · Aula 5 de 9 · Atualizado em 2026-10-04.
Objetivo#
Ao final desta aula você vai validar assinatura HMAC — e deduplicar o evento do lado do seu sistema antes de agir sobre ele.
Vídeo#
Identificador no manifesto: acd-420-05-webhooks-hmac · duração-alvo 6 min · tela do produto: /relatorios.
Roteiro de gravação (5 capítulos):
| # | Minutagem-alvo | Capítulo | Tela do produto |
|---|---|---|---|
| 1 | 0:00–0:40 | Abertura: título + selo Preview. Webhook de saída do alerta × API de assinatura de webhooks (Roadmap). | /dashboards/[id] |
| 2 | 0:40–2:00 | Configurar: canal webhook no alerta do visual, URL https do seu sistema, e o segredo mostrado na tela. | /dashboards/[id] |
| 3 | 2:00–3:20 | O corpo do POST e os dois cabeçalhos; verificar a assinatura timing-safe antes de confiar no corpo. | editor externo |
| 4 | 3:20–4:40 | Proteções: guarda SSRF na URL, 3 tentativas com backoff, 4xx permanente. | /dashboards/[id] |
| 5 | 4:40–6:00 | Dedupe pelo idempotencyKey, rotação do segredo e erros comuns. Encerramento com o "faça você mesmo". | editor externo |
Conteúdo#
Webhook de saída ≠ assinatura de webhooks
Duas coisas com nome parecido, estados diferentes — e trocá-las custa tempo:
| Webhook de saída do alerta | API de assinatura de webhooks | |
|---|---|---|
| O que é | o alerta faz um POST no seu sistema quando a regra é violada | você assinaria eventos do workspace para receber tudo que acontece |
| Estado | existe, assinado com HMAC-SHA256 — Preview | Roadmap — não disponível |
| Como obter hoje | configurar o canal webhook num alerta | para reagir a dados, use polling do feed OData ou as assinaturas/alertas do produto |
Esta aula é sobre a primeira coluna. O webhook de saída é o primeiro egress "para você" do produto: ele manda dado para fora, mas para um destino seu, sob seu controle — não para um modelo de linguagem. Nada aqui envolve LLM.
Configurar
No alerta do visual (Aula 4), escolha o canal webhook e informe em "Destinatário" a URL https do seu endpoint. A tela mostra o segredo do seu workspace — é o valor que você cola no seu sistema. Esse segredo é derivado por workspace e não existe em banco; a rotação é feita por quem opera a plataforma (bump da versão do segredo + redeploy), e depois dela a tela passa a mostrar o valor novo para você recolar. Vazamento do segredo de um workspace não compromete os demais.
O que chega no seu endpoint
Dois cabeçalhos e um corpo JSON:
| Cabeçalho | Valor |
|---|---|
X-Ingestia-Signature | sha256=<hex do HMAC-SHA256 do corpo bruto> |
X-Ingestia-Timestamp | instante do envio, em ISO |
O corpo traz: evento, dashboardId, widgetId, regra (a regra avaliada), valor, limiar, motivo (o texto em português), firedAt e idempotencyKey.
Verifique antes de confiar. Recalcule o HMAC sobre o corpo bruto (não sobre o JSON re-serializado — reserializar muda bytes e quebra a assinatura) e compare em tempo constante:
// Node.js — verifique ANTES de confiar no corpo. O segredo vem da tela de alertas.
import { createHmac, timingSafeEqual } from "node:crypto";
const SEGREDO = process.env.INGESTIA_ALERT_WEBHOOK_SECRET;
export function verificar(corpoBruto, headerAssinatura) {
const esperada =
"sha256=" + createHmac("sha256", SEGREDO).update(corpoBruto, "utf8").digest("hex");
const a = Buffer.from(esperada, "utf8");
const b = Buffer.from(String(headerAssinatura ?? ""), "utf8");
return a.length === b.length && timingSafeEqual(a, b);
}E deduplique: guarde o idempotencyKey do corpo e ignore o que já processou. O mesmo evento nunca deve virar duas ações do seu lado, mesmo que a entrega se repita.
Proteções que você vai sentir
- Guarda SSRF. A URL é dado do usuário e o
POSTsai da nossa rede, então a validação é dura: sóhttps, sem credenciais na URL, semlocalhostou domínio interno, sem IP privado, de loopback, link-local ou de metadata — em IPv4 e IPv6, inclusive nas formas decimal e hexadecimal normalizadas. Destino interno é rejeitado no cadastro, não na hora do envio. - Retry com teto. São 3 tentativas com backoff exponencial e jitter.
5xx,408e429são transientes e valem re-tentar. Qualquer outro4xxé permanente: re-tentar não conserta URL errada, rota inexistente ou token seu expirado. Falha final marca o evento de alerta comofailed. - Timeout curto. Cada
POSTtem timeout, porque o envio roda dentro do refresh do painel e não pode segurar o cron. Responda2xxrápido e processe depois — endpoint lento é tratado como falha.
Em ambiente não produtivo o POST é barrado
Fora de produção, o efeito externo do webhook é bloqueado de propósito — o produto não bate no seu endpoint real a partir de um ambiente de treino. A guarda de URL continua valendo (e o erro de URL inválida continua sendo o erro mais útil), mas o envio em si é recusado com motivo explícito. Nada finge sucesso.
Exemplo Aurora
maria liga um segundo alerta no KPI de receita da Aurora: valor < 40000, canal webhook, destino https://alertas.aurora.example/ingestia. Do outro lado, o time da Aurora recebe:
{
"evento": "alerta",
"motivo": "\"Receita\" abaixo de 40000 (38120)",
"valor": 38120,
"limiar": 40000,
"firedAt": "2026-10-05T11:03:00.000Z",
"idempotencyKey": "…"
}O endpoint verifica a assinatura, encontra o idempotencyKey já processado (o refresh anterior tinha enviado o mesmo evento) e não abre um segundo chamado interno. Quando alguém tentou cadastrar http://localhost:4000/hook para testar, a URL foi recusada no cadastro — a guarda SSRF não aceita http nem localhost.
Erros comuns
| Erro | Por que é erro |
|---|---|
| Confiar no corpo antes de verificar a assinatura | qualquer um poderia postar no seu endpoint |
| Recalcular o HMAC sobre o JSON re-serializado | a assinatura é sobre o corpo bruto; reserializar muda bytes |
Comparar assinaturas com === | compare em tempo constante (timingSafeEqual) |
Não deduplicar pelo idempotencyKey | uma entrega repetida viraria duas ações do seu lado |
Cadastrar http://, localhost ou IP interno | a guarda SSRF recusa — e isso é proteção, não bug |
Esperar retry depois de um 404/401 seu | 4xx (≠ 408/429) é permanente: conserte a URL ou a autorização |
| Processar o evento dentro da requisição e demorar | o timeout é curto; responda 2xx e processe em fila |
| Esperar uma API de assinatura de webhooks | é Roadmap; hoje o caminho é o canal webhook do alerta |
Estado e gates
O webhook de saída é coberto por testes determinísticos: assinatura HMAC, guarda SSRF (incluindo formas normalizadas de IP), idempotência e política de retry. O teto do produto é Preview e a entrega real é NÃO MEDIDO — em treino o POST externo é barrado. A API pública de assinatura de webhooks continua Roadmap.
Faça você mesmo#
Você vai precisar de um endpoint https acessível pela internet (um receptor de teste serve). No workspace de treino Aurora Varejo (abra /dashboards no console):
- No visual de Receita, abra Alerta e troque o canal para webhook, com a URL
httpsdo seu receptor de teste. - Tente antes, de propósito, cadastrar
http://localhost:4000/hooke confirme que a URL é recusada — anote o motivo. - Copie o segredo do workspace mostrado na tela para o seu receptor (nunca para um repositório).
- Implemente a verificação da assinatura sobre o corpo bruto, com comparação em tempo constante.
- Force a violação (atualize os dados com a receita abaixo do limiar) e confira os cabeçalhos
X-Ingestia-SignatureeX-Ingestia-Timestampno que chegou. - Altere um byte do corpo recebido e rode sua verificação de novo: ela tem que falhar.
- Guarde o
idempotencyKeye simule uma entrega repetida: seu endpoint deve ignorar a segunda. - Responda
500de propósito numa tentativa e observe o retry; depois responda404e confirme que não há retry.
Atenção: este exercício depende de gate — em ambiente não produtivo o POST externo é barrado; nesse caso, valide a verificação com o corpo e a assinatura de uma execução registrada, em vez de esperar a chamada chegar.
Você terminou quando sua verificação aceitar o corpo original, rejeitar o corpo alterado em um byte, e você conseguir explicar por que um 404 do seu lado não é re-tentado.
Checagem rápida#
Três perguntas no final da aula, corrigidas no servidor. A aula só conta como concluída depois da checagem.
Documentação relacionada#
Capacidades ensinadas#
C08.5 — o selo exibido na aula é sempre o estado mais conservador entre as capacidades citadas; nada aqui é "GA".