Integração

Webhooks

Um único endpoint de produção recebe eventos operacionais da Meta e eventos internos da HookCloud.

Arquitetura de entrega

Metamessages, statuses, calls
Webhook do partnerpor phone_number_id
HookCloudtemplate.updated, lifecycle e saúde
Mesmo endpointassinado com HMAC

A URL salva no Partner Portal é aplicada às instâncias ativas. Quando a URL muda, a HookCloud migra os callbacks na Meta e mostra o progresso.

Referências: Webhooks Meta · Override de callback

Desafio GET da Meta

javascript
app.get('/webhooks/whatsapp', (req, res) => {
  const mode = req.query['hub.mode'];
  const token = req.query['hub.verify_token'];
  const challenge = req.query['hub.challenge'];

  if (mode === 'subscribe' && token === process.env.META_VERIFY_TOKEN) {
    return res.status(200).send(challenge);
  }
  return res.sendStatus(403);
});

Eventos da Meta com e sem telefone

A Meta envia os campos assinados no app. Preserve o payload de cada campo para o uso que seu produto oferece, inclusive business_username_updates, eventos de templates e coexistência. Não transforme todos os eventos em mensagens.

javascript
// Execute após verificar a origem/autenticidade do webhook.
// Persistir na fila deve terminar antes do HTTP 200.
export function parseMetaEvents(payload) {
  const events = [];
  for (const entry of payload.entry ?? []) {
    for (const change of entry.changes ?? []) {
      const value = change.value ?? {};
      const base = { wabaId: entry.id, phoneNumberId: value.metadata?.phone_number_id };
      if (change.field !== 'messages') {
        events.push({ ...base, kind: 'meta_event', field: change.field, value });
        continue;
      }
      for (const message of value.messages ?? []) {
        const contact = (value.contacts ?? []).find(c =>
          (message.from_user_id && c.user_id === message.from_user_id) ||
          (message.from && c.wa_id === message.from));
        events.push({ ...base, kind: 'inbound', message,
          userId: message.from_user_id ?? contact?.user_id ?? null,
          parentUserId: message.from_parent_user_id ?? contact?.parent_user_id ?? null,
          username: contact?.profile?.username ?? null,
          phone: message.from || contact?.wa_id || null });
      }
      for (const status of value.statuses ?? []) {
        events.push({ ...base, kind: 'status', status,
          userId: status.recipient_user_id ?? null,
          parentUserId: status.recipient_parent_user_id ?? null,
          phone: status.recipient_id || null });
      }
    }
  }
  return events;
}

O parser acima é um adaptador de dados. O receptor HTTP precisa validar a origem/assinatura conforme a integração, gravar os eventos de forma durável e então responder 200. Responder antes da gravação pode perder o evento se o processo cair. O hub.verify_token do desafio GET não valida a assinatura HMAC de um POST.

Para mensagens, deduplique pelo wamid dentro da instância. Para status, use também o estado e preserve a progressão sem regredir de read para sent se eventos chegarem fora de ordem. Não procure uma pessoa apenas pelo telefone; ele pode estar ausente.

Referência: Campos e exceções dos webhooks de identidade.

Eventos HookCloud e HMAC

A HookCloud inclui os headers abaixo. Verifique a assinatura usando o corpo bruto e rejeite timestamps antigos para reduzir replay.

HeaderDescrição
X-HookCloud-EventTipo do evento
X-HookCloud-DeliveryID único da entrega
X-HookCloud-TimestampUnix timestamp
X-HookCloud-Signaturesha256=HMAC_SHA256(secret, timestamp.rawBody)
Node.jsjavascript
import crypto from 'node:crypto';

export function verifyHookCloudWebhook({ rawBody, timestamp, signature, secret }) {
  const age = Math.abs(Date.now() / 1000 - Number(timestamp));
  if (!Number.isFinite(age) || age > 300) return false;

  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${timestamp}.${rawBody}`)
    .digest('hex');

  const received = String(signature || '').replace(/^sha256=/, '');
  if (expected.length !== received.length) return false;
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(received));
}

Evento de template

json
{
  "id": "UUID_DO_EVENTO",
  "type": "hookcloud.meta.template.updated",
  "created_at": "2026-07-21T12:00:00Z",
  "partner_id": "UUID_DO_PARTNER",
  "data": {
    "meta_waba_id": "WABA_ID",
    "template_id": "123456789",
    "name": "confirmacao_agendamento",
    "language": "pt_BR",
    "previous_status": "PENDING",
    "status": "APPROVED",
    "category": "UTILITY",
    "quality_status": "GREEN",
    "sendable": true
  }
}

Use id para idempotência. Ao receber a mudança, atualize a tela e reconcilie com list-partner-meta-templates.

Troca segura da URL

  1. Implemente GET e POST na URL nova.
  2. Teste com validate-partner-webhook-endpoint.
  3. Aplique com update-partner-webhook-endpoint.
  4. Acompanhe a migração.
  5. Reprocesse somente as falhas.

Boas práticas de produção

  • Responder HTTP 2xx em poucos segundos
  • Enfileirar processamento pesado
  • Deduplicar pelo ID da mensagem e ID da entrega
  • Não seguir redirects no seu validador
  • Registrar falhas sem conteúdo sensível
  • Monitorar taxa de HTTP 4xx/5xx
  • Rotacionar segredos com processo controlado
Esta página ajudou?Use o Partner Portal para suporte e compartilhe o link desta seção.