Webhooks

Cadastre URLs HTTPS em Configurações → API → Webhooks (ou pela API) e escolha os eventos. Cada entrega é um POST JSON com X-VDCRM-Event, X-VDCRM-Delivery, X-VDCRM-Timestamp e X-VDCRM-Signature: t=<unix>,v1=<hmac>, onde o HMAC SHA-256 é calculado sobre "<t>.<corpo>" com o segredo do endpoint. Rejeite assinaturas com mais de 5 minutos. Responda 2xx em até 10 s; caso contrário tentamos de novo após 30s, 2min, 10min, 30min, 1h, 2h, 4h, 8h, 8h (cerca de 24 h).

message.received
message.status
conversation.assigned
conversation.closed
contact.opted_out
template.status
campaign.started
campaign.completed
campaign.paused
phone_number.quality_changed
phone_number.status_changed
phone_number.connected
connect_session.failed
flow.completed
deal.stage_changed
report.ready
{
  "id": "5b1e…",
  "event": "message.received",
  "created_at": "2026-11-03T15:02:00Z",
  "operation_id": "…",
  "data": {
    "message_id": "m1…",
    "conversation_id": "9f1e…",
    "wamid": "wamid.HBg…",
    "type": "text",
    "content": {
      "text": "Olá!"
    }
  }
}
import crypto from "node:crypto";

// Use o corpo BRUTO da requisição (antes de JSON.parse).
export function verifyVdcrm(rawBody, header, secret) {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  const t = Number(parts.t);
  if (!t || Math.abs(Date.now() / 1000 - t) > 300) return false; // anti-replay: 5 min
  const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1 ?? ""));
}