Pular para o conteúdo

Webhook de saída com HMAC

Aula 5 de 96 minOperarAtualizada em 2026-10-04

Vídeo em produção

A gravação desta aula está no lote de produção. O objetivo, o exercício e a documentação já valem. Duração-alvo: 6 min.

Objetivo: Validar assinatura HMAC

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-alvoCapítuloTela do produto
10:00–0:40Abertura: título + selo Preview. Webhook de saída do alerta × API de assinatura de webhooks (Roadmap)./dashboards/[id]
20:40–2:00Configurar: canal webhook no alerta do visual, URL https do seu sistema, e o segredo mostrado na tela./dashboards/[id]
32:00–3:20O corpo do POST e os dois cabeçalhos; verificar a assinatura timing-safe antes de confiar no corpo.editor externo
43:20–4:40Proteções: guarda SSRF na URL, 3 tentativas com backoff, 4xx permanente./dashboards/[id]
54:40–6:00Dedupe 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 alertaAPI de assinatura de webhooks
O que éo alerta faz um POST no seu sistema quando a regra é violadavocê assinaria eventos do workspace para receber tudo que acontece
Estadoexiste, assinado com HMAC-SHA256 — PreviewRoadmap — não disponível
Como obter hojeconfigurar o canal webhook num alertapara 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çalhoValor
X-Ingestia-Signaturesha256=<hex do HMAC-SHA256 do corpo bruto>
X-Ingestia-Timestampinstante 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:

js
// 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 POST sai da nossa rede, então a validação é dura: só https, sem credenciais na URL, sem localhost ou 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, 408 e 429 são transientes e valem re-tentar. Qualquer outro 4xx é permanente: re-tentar não conserta URL errada, rota inexistente ou token seu expirado. Falha final marca o evento de alerta como failed.
  • Timeout curto. Cada POST tem timeout, porque o envio roda dentro do refresh do painel e não pode segurar o cron. Responda 2xx rá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:

json
{
  "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

ErroPor que é erro
Confiar no corpo antes de verificar a assinaturaqualquer um poderia postar no seu endpoint
Recalcular o HMAC sobre o JSON re-serializadoa assinatura é sobre o corpo bruto; reserializar muda bytes
Comparar assinaturas com ===compare em tempo constante (timingSafeEqual)
Não deduplicar pelo idempotencyKeyuma entrega repetida viraria duas ações do seu lado
Cadastrar http://, localhost ou IP internoa guarda SSRF recusa — e isso é proteção, não bug
Esperar retry depois de um 404/401 seu4xx (≠ 408/429) é permanente: conserte a URL ou a autorização
Processar o evento dentro da requisição e demoraro 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):

  1. No visual de Receita, abra Alerta e troque o canal para webhook, com a URL https do seu receptor de teste.
  2. Tente antes, de propósito, cadastrar http://localhost:4000/hook e confirme que a URL é recusada — anote o motivo.
  3. Copie o segredo do workspace mostrado na tela para o seu receptor (nunca para um repositório).
  4. Implemente a verificação da assinatura sobre o corpo bruto, com comparação em tempo constante.
  5. Force a violação (atualize os dados com a receita abaixo do limiar) e confira os cabeçalhos X-Ingestia-Signature e X-Ingestia-Timestamp no que chegou.
  6. Altere um byte do corpo recebido e rode sua verificação de novo: ela tem que falhar.
  7. Guarde o idempotencyKey e simule uma entrega repetida: seu endpoint deve ignorar a segunda.
  8. Responda 500 de propósito numa tentativa e observe o retry; depois responda 404 e 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".

Carregando seu progresso…