Developers
Webhooks v1
OpenAPI
HMAC-SHA256

Webhooks assinados, com retries determinísticos.

Cada evento é entregue via POST JSON com assinatura HMAC-SHA256 sobre {timestamp}.{body}. Rejeite requisições com clock skew maior que 5 minutos e valide a assinatura em tempo constante.

Método & rota

POST /sua-url

Header de assinatura

X-DestrIA-Signature

Header de timestamp

X-DestrIA-Timestamp

Idempotency-Key

Estável por evento (evt_...)

Retries

Backoff exponencial até 5x

Rotação de segredo

Grace window 24h / 7d / 30d

Catálogo de eventos

Selecione um tipo para ver o payload esperado. Todos compartilham envelope comum: id, type, created_at, workspace_id, data.

email.delivered

Entrega confirmada pelo servidor SMTP destino (250 OK).

{
  "id": "evt_01HZY3K1G3M2N4P5Q6R7S8T9U0",
  "type": "email.delivered",
  "created_at": "2026-07-03T14:22:10.482Z",
  "workspace_id": "ws_abc123",
  "data": {
    "message_id": "msg_9f2a7c",
    "campaign_id": "cmp_launch_q3",
    "contact_id": "ct_82311",
    "smtp_id": "smtp_primary",
    "recipient": "[email protected]",
    "subject": "Bora conversar essa semana?"
  }
}

Verificador HMAC online

Cole a assinatura recebida, o timestamp do header e o corpo bruto (raw body) para conferir se a validação bate. Tudo acontece localmente no navegador via Web Crypto — nada é enviado para nossos servidores.

Receiver de referência (Node/Deno/Edge)

Cópia direta para produção. Valida timestamp (5 min), compara em tempo constante e trata retries idempotentes por evt_id.

import { createHmac, timingSafeEqual } from "node:crypto";

const WEBHOOK_SECRET = process.env.DESTRIA_WEBHOOK_SECRET!;
const seen = new Set<string>(); // troque por Redis/DB em produção

export async function handler(req: Request): Promise<Response> {
  const raw = await req.text();
  const sig = req.headers.get("x-destria-signature") ?? "";
  const ts = req.headers.get("x-destria-timestamp") ?? "";

  const now = Math.floor(Date.now() / 1000);
  if (Math.abs(now - Number(ts)) > 300) {
    return new Response("stale timestamp", { status: 401 });
  }

  const expected = createHmac("sha256", WEBHOOK_SECRET)
    .update(`${ts}.${raw}`)
    .digest("hex");

  const a = Buffer.from(sig);
  const b = Buffer.from(expected);
  if (a.length !== b.length || !timingSafeEqual(a, b)) {
    return new Response("invalid signature", { status: 401 });
  }

  const event = JSON.parse(raw) as { id: string; type: string };
  if (seen.has(event.id)) return new Response("ok", { status: 200 });
  seen.add(event.id);

  // processar evento...
  return new Response("ok", { status: 200 });
}