Recebendo Mensagens via Webhook

Visão geral

Quando um usuário do WhatsApp envia uma mensagem para um dos seus números conectados, o Muchaw DEV roteia essa mensagem para a sua URL de webhook. Este guia mostra como configurar um servidor de webhook que recebe e processa esses eventos com segurança.

Passo 1 — Crie um webhook

$curl -X POST https://dev.muchau.com.br/api/webhooks \
> -H "Authorization: Bearer $MUCHAW_API_KEY" \
> -H "Content-Type: application/json" \
> -d '{
> "url": "https://seu-servidor.com/webhooks/whatsapp",
> "events": [
> "message.received",
> "message.delivered",
> "message.read",
> "message.failed"
> ]
> }'

Salve o secret da resposta — você precisará dele para verificar as requisições recebidas.

$export MUCHAW_WEBHOOK_SECRET=whsec_...

Passo 2 — Construa o handler do webhook

1import express from "express";
2import crypto from "node:crypto";
3
4const app = express();
5
6function verifySignature(rawBody, signature, secret) {
7 if (!signature?.startsWith("sha256=")) return false;
8 const expected = crypto
9 .createHmac("sha256", secret)
10 .update(rawBody)
11 .digest("hex");
12 return crypto.timingSafeEqual(
13 Buffer.from(signature.slice(7)),
14 Buffer.from(expected)
15 );
16}
17
18// Use raw body parser para preservar os bytes originais na verificação de assinatura
19app.post(
20 "/webhooks/whatsapp",
21 express.raw({ type: "application/json" }),
22 (req, res) => {
23 // 1. Verificar assinatura
24 const signature = req.headers["x-muchaw-signature"];
25 if (!verifySignature(req.body, signature, process.env.MUCHAW_WEBHOOK_SECRET)) {
26 return res.status(401).json({ error: "Assinatura inválida" });
27 }
28
29 // 2. Responder imediatamente (antes de processar)
30 res.sendStatus(200);
31
32 // 3. Parsear e processar
33 const event = JSON.parse(req.body.toString());
34 handleEvent(event);
35 }
36);
37
38function handleEvent(event) {
39 switch (event.event) {
40 case "message.received":
41 console.log("Nova mensagem de", event.data.fromNumber, ":", event.data.body);
42 break;
43 case "message.delivered":
44 console.log("Mensagem", event.data.id, "entregue");
45 break;
46 case "message.read":
47 console.log("Mensagem", event.data.id, "lida");
48 break;
49 case "message.failed":
50 console.error("Falha na entrega da mensagem", event.data.id);
51 break;
52 }
53}
54
55app.listen(3001, () => console.log("Servidor de webhook na porta 3001"));

Sempre responda com 200 antes de processar. O Muchaw DEV aguarda até 10 segundos por uma resposta. Se seu processamento demorar mais, responda imediatamente e processe o evento de forma assíncrona.

Passo 3 — Exponha seu servidor publicamente

Para desenvolvimento local, use um tunnel como o ngrok:

$ngrok http 3001
$# → Forwarding: https://abc123.ngrok.io → localhost:3001

Atualize a URL do seu webhook para a URL do ngrok.

Passo 4 — Teste o webhook

$curl -X POST "https://dev.muchau.com.br/api/webhooks/<webhook_id>/test" \
> -H "Authorization: Bearer $MUCHAW_API_KEY"

Resposta:

1{
2 "success": true,
3 "statusCode": 200,
4 "responseTimeMs": 87,
5 "message": "Test webhook delivered successfully"
6}

Estrutura do evento de webhook

Todos os eventos seguem o mesmo envelope:

1{
2 "event": "message.received",
3 "timestamp": "2024-01-15T10:05:00.000Z",
4 "data": { ... }
5}

O formato de data depende do tipo de evento. Para eventos message.*, é um objeto Message. Para eventos number.*, é um objeto Number.

Lidando com retries

O Muchaw DEV tenta novamente os eventos até 3 vezes com backoff exponencial. Seu handler deve ser idempotente — processar o mesmo evento duas vezes não deve causar efeitos colaterais (registros duplicados, envios duplicados, etc.).

Use event.data.id como chave de deduplicação:

1const processed = new Set();
2function handleEvent(event) {
3 const key = `${event.event}:${event.data.id}`;
4 if (processed.has(key)) return;
5 processed.add(key);
6 // processar...
7}

Em produção, use Redis ou seu banco de dados para deduplicação.