Webhooks de Origem

Como registrar, validar assinatura HMAC e garantir entrega idempotente de eventos em tempo real.

O que é webhook?

Webhook é HTTP POST que a VZD envia para seu servidor toda vez que algo acontece: cliente envia mensagem, confirma entrega, responde pelo celular (echo), ou template muda de estado.

Sem webhook você depende de polling constante (GET /messages, GET /statuses) — latência alta e ineficiente. Com webhook você recebe em milissegundos.

Fluxo de configuração

1

Registre sua URL

curl -X PUT https://api.vzdapplication.com/api/whatsapp/webhook \
  -H "Authorization: Bearer vzd_SUA_CHAVE_AQUI" \
  -H "Content-Type: application/json" \
  -d '{
    "webhook_url": "https://seu-dominio.com.br/webhooks/whatsapp"
  }'
      

VZD retorna um segredo (aparece uma única vez):

{
  "webhook_url": "https://seu-dominio.com.br/webhooks/whatsapp",
  "webhook_secret": "sk_whk_abc123..."
}
      
Guarde o segredo com segurança. Será necessário para validar assinaturas. Não há rota para recuperar depois.
2

Prepare seu endpoint para receber POST

Seu servidor receberá:

POST /webhooks/whatsapp HTTP/1.1
Host: seu-dominio.com.br
X-VZD-Signature: sha256=abc123def456...
Content-Type: application/json
Content-Length: 512

{
  "event": "whatsapp.message.received",
  "event_id": "evt_9f2c0a1b",
  "occurred_at": "2024-01-15T10:00:00.000Z",
  "channel": {
    "phone_number_id": "5511999887766",
    "waba_id": "100234567890"
  },
  "data": { ... }
}
      
3

Valide a assinatura HMAC

Antes de processar qualquer evento, valide que veio de fato da VZD usando o segredo e o header X-VZD-Signature.

Algoritmo:

  1. Capture o corpo bruto da requisição (bytes exatos como foram enviados)
  2. Compute HMAC-SHA256 usando a chave (seu segredo) e o corpo
  3. Compare com o valor em X-VZD-Signature (sem "sha256=" no prefixo)
  4. Use comparação em tempo constante para evitar timing attacks

Exemplo em Node.js:

const crypto = require('crypto');

const payload = req.rawBody; // corpo bruto em bytes
const signature = req.headers['x-vzd-signature'];
const secret = process.env.WEBHOOK_SECRET;

// Extrai hex da assinatura
const [algorithm, hash] = signature.split('=');
if (algorithm !== 'sha256') {
  return res.status(401).send('Unknown algorithm');
}

// Computa HMAC
const computed = crypto
  .createHmac('sha256', secret)
  .update(payload)
  .digest('hex');

// Compara em tempo constante
const isValid = crypto.timingSafeEqual(
  Buffer.from(computed),
  Buffer.from(hash)
);

if (!isValid) {
  return res.status(401).send('Invalid signature');
}

// OK, processa evento
handleWebhookEvent(req.body);
      

Exemplo em Python:

import hmac
import hashlib
from flask import request

payload = request.data  # corpo bruto em bytes
signature = request.headers.get('X-VZD-Signature')
secret = os.environ['WEBHOOK_SECRET']

algorithm, hash_hex = signature.split('=')
if algorithm != 'sha256':
    return 'Unknown algorithm', 401

computed = hmac.new(
    secret.encode(),
    payload,
    hashlib.sha256
).hexdigest()

# Compara em tempo constante
if not hmac.compare_digest(computed, hash_hex):
    return 'Invalid signature', 401

# OK, processa evento
handle_webhook_event(request.json)
      
Armadilha: Reserializar o JSON com JSON.stringify(request.body) muda espaçamento e ordem de chaves. Use o corpo bruto. Se você estiver usando um framework que já parsou JSON, capture o corpo antes do parse.
4

Deduplicação por event_id

VZD envia eventos com ID determinístico. Se perder a confirmação, reenviará o mesmo evento com o mesmo ID.

Para garantir idempotência:

  1. Registre cada event_id em um banco de dados (até 7 dias)
  2. Se receber evento com ID já visto, responda 200 imediatamente
  3. Isso protege contra duplicação em caso de retry
const eventId = req.body.event_id;

// Consulta banco
const exists = await db.events.findOne({ id: eventId });
if (exists) {
  // Já processado
  return res.status(200).send('OK');
}

// Processa
await handleEvent(req.body);

// Grava
await db.events.create({ id: eventId, processedAt: new Date() });

res.status(200).send('OK');
      
Retenção: Guarde event_ids por 7 dias. Depois pode apagar.
5

Responda rápido com 200

VZD espera resposta HTTP 200-299 em segundos. Processe o evento em background.

// Responde logo
res.status(200).send('OK');

// Processa depois
process.nextTick(async () => {
  await updateCRMWithMessage(req.body.data);
});
      

Tipos de evento

Evento Quando? Campo data
whatsapp.message.received Cliente final enviou mensagem { wamid, from, type, text/media, ... }
whatsapp.message.echoed Alguém respondeu pelo celular (Coexistência) { wamid, from, direction: "OUTBOUND", origin: "BUSINESS_APP_ECHO", text, ... }
whatsapp.message.status Status de entrega (SENT, DELIVERED, READ, FAILED) { wamid, status, failureReason }
whatsapp.template.status Template mudou de estado (APPROVED, REJECTED, PAUSED, ...) { template_name, status, rejection_reason }

Estrutura do evento

{
  "event": "whatsapp.message.received",
  "event_id": "evt_9f2c0a1b",
  "occurred_at": "2024-01-15T10:00:00.000Z",
  "channel": {
    "phone_number_id": "5511999887766",
    "waba_id": "100234567890"
  },
  "data": {
    "wamid": "wamid.HhBmRGVmAUH7AglMkZGVzQ==",
    "from": "5511987654321",
    "direction": "INBOUND",
    "origin": "CLOUD_API",
    "type": "text",
    "text": "Olá! Qual é o horário?",
    "timestamp": "2024-01-15T10:00:00.000Z",
    "sentAt": "2024-01-15T10:00:00.000Z"
  }
}
    

Filtrar echo em suas automações

Armadilha crítica: se você tem IA respondendo automaticamente, ela pode responder ao próprio echo (resposta do celular).

A solução é filtrar por origin antes de entregar:

if (event.data.origin === 'BUSINESS_APP_ECHO') {
  // Cliente respondeu pelo celular — ignorar para IA
  return res.status(200).send('OK');
}

// Só aqui chega a IA
await aiService.respond(event.data);
    
Teste isso antes de ir a produção: Envie mensagem pela API, responda pelo celular, confira que o evento chega com origin = BUSINESS_APP_ECHO e a IA não responde.

Troubleshooting

Não estou recebendo webhooks

Assinatura inválida

Eventos duplicados