Ir para o conteúdo
WhatsMarketingDeveloper Hub

Webhooks

Um webhook é uma URL https sua para a qual a WhatsMarketing envia um POST cada vez que ocorre um evento ao qual você se inscreveu. Eles são criados em Integrações → Webhooks e API (até 10 por organização), com um segredo whsec_… que é exibido uma única vez.

Eventos

Evento Quando Status
contact.created Um contato é criado (painel, API, importação ou entrada pelo WhatsApp) Ativo
contact.updated Os dados de um contato mudam Ativo
contact.stage_changed Um contato muda de etapa do ciclo de vida Ativo
campaign.finished Uma campanha termina de ser enviada Ativo
campaign.replied Um destinatário responde a uma campanha Em breve
conversation.opened, conversation.assigned, conversation.closed Ciclo de uma conversa Em breve
message.received, message.sent Mensagens recebidas e enviadas Em breve
automation.run Uma automação é executada Em breve
bot.handoff, bot.message Um agente de IA transfere para uma pessoa ou responde Em breve

Você já pode se inscrever nos eventos «em breve»: eles começarão a chegar quando forem ativados, sem mexer em nada.

O que você recebe

HTTP
POST /sua-rota HTTP/1.1
Content-Type: application/json
User-Agent: WhatsMarketing-Webhooks/1.0
X-WM-Event: contact.created
X-WM-Delivery: 3f4e7f0a-9b1c-4d2e-8f3a-4b5c6d7e8f90
X-WM-Signature: t=1760436060,v1=5d41402abc4b2a76b9719d911017c592…
JSON
{
  "id": "3f4e7f0a-9b1c-4d2e-8f3a-4b5c6d7e8f90",
  "event": "contact.created",
  "createdAt": "2026-10-14T10:21:00.000Z",
  "data": {
    "organizationId": "f0e1d2c3-b4a5-4968-8776-655443322110",
    "contactId": "0b1c2d3e-5f6a-4b7c-8d9e-0f1a2b3c4d5e",
    "fullName": "Laura Pérez",
    "primaryPhone": "+34612345678",
    "primaryEmail": "laura@ejemplo.com",
    "lifecycleStage": "Lead nuevo"
  }
}

id e X-WM-Delivery identificam a entrega: se você receber duas vezes o mesmo valor (por uma nova tentativa), processe só a primeira. data traz o essencial para saber o que aconteceu; para a ficha completa, peça GET /contacts/{contactId}.

Evento data
contact.created organizationId, contactId, fullName, primaryPhone, primaryEmail, lifecycleStage
contact.updated o mesmo mais changedFields (os campos que mudaram)
contact.stage_changed o mesmo mais previousStage
campaign.finished organizationId, campaignId, name, status, sent, failed

Verificar a assinatura

A assinatura protege contra alguém enviar eventos falsos para você. É o HMAC-SHA256 do texto "<t>.<corpo bruto>" com o seu segredo, em hexadecimal. Calcule com o corpo exatamente como chegou (sem reformatar o JSON), compare em tempo constante e descarte marcas de tempo com mais de 5 minutos.

Node.js
import crypto from 'node:crypto';

function verificar(corpoBruto, cabecalho, segredo) {
  const partes = Object.fromEntries(cabecalho.split(',').map((p) => p.split('=')));
  const esperado = crypto.createHmac('sha256', segredo).update(`${partes.t}.${corpoBruto}`).digest('hex');
  const recente = Math.abs(Date.now() / 1000 - Number(partes.t)) < 300;
  return recente && crypto.timingSafeEqual(Buffer.from(esperado), Buffer.from(partes.v1));
}

// Express: use o corpo bruto, não o JSON já parseado.
app.post('/webhooks/wm', express.raw({ type: 'application/json' }), (req, res) => {
  if (!verificar(req.body.toString(), req.header('X-WM-Signature'), process.env.WM_WEBHOOK_SECRET)) {
    return res.status(401).end();
  }
  res.status(200).end(); // responda já; processe depois
  const evento = JSON.parse(req.body);
  // …
});
Python
import hmac, hashlib, time

def verificar(corpo_bruto: bytes, cabecalho: str, segredo: str) -> bool:
    partes = dict(p.split("=") for p in cabecalho.split(","))
    esperado = hmac.new(segredo.encode(), f"{partes['t']}.".encode() + corpo_bruto, hashlib.sha256).hexdigest()
    recente = abs(time.time() - int(partes["t"])) < 300
    return recente and hmac.compare_digest(esperado, partes["v1"])
PHP
<?php
function verificar(string $corpoBruto, string $cabecalho, string $segredo): bool {
  parse_str(str_replace(',', '&', $cabecalho), $partes);
  $esperado = hash_hmac('sha256', $partes['t'] . '.' . $corpoBruto, $segredo);
  $recente = abs(time() - (int) $partes['t']) < 300;
  return $recente && hash_equals($esperado, $partes['v1']);
}

Novas tentativas e pausa

  • Consideramos recebida qualquer resposta 2xx em menos de 10 segundos.
  • Se falhar, tentamos de novo 5 vezes: em 1 minuto, em 5 minutos, em 30, em 2 horas e em 12 horas.
  • Dez entregas esgotadas seguidas pausam o webhook. Você o reativa pelo painel; de lá também pode tentar novamente as que falharam.
  • As entregas ficam guardadas por 7 dias: no painel você vê cada uma com a sua resposta.

Testar localmente

  1. Suba o seu servidor localmente.
  2. Abra um túnel (ngrok http 3000 ou Cloudflare Tunnel) e copie a URL https.
  3. Crie o webhook com essa URL e contact.created.
  4. Crie um contato pelo painel ou com POST /contacts: em segundos você verá o POST no seu console.