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).
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 });
}