Appearance
Integração WhatsApp Cloud API (Meta)
Contrato HTTP da Cloud API oficial da Meta: verificação do webhook, payload de entrada, envio via Graph API e roteamento por provedor.
Visão Geral
O bot suporta dois provedores de WhatsApp em paralelo, atrás do mesmo endpoint /webhook:
- Z-API (
z-api.io) — ver zapi-integration.md - WhatsApp Cloud API (Meta) — API oficial da Meta (
graph.facebook.com)
O provedor de cada mensagem é detectado pelo formato do payload e propagado no campo IncomingMessage.provider ("zapi" | "meta"). A resposta sai pelo mesmo provedor de onde a mensagem entrou.
As credenciais da Meta ficam em variáveis de ambiente: WHATSAPP_VERIFY_TOKEN (verificação), META_PHONE_NUMBER_ID, META_ACCESS_TOKEN, META_GRAPH_BASE_URL (default https://graph.facebook.com) e META_GRAPH_VERSION (default v21.0).
Verificação do Webhook (GET /webhook)
Ao salvar o webhook no painel da Meta, ela envia:
GET /webhook?hub.mode=subscribe&hub.challenge=<random>&hub.verify_token=<token>MetaVerificationHandler (src/modules/gateway/handlers/meta-verification.handler.ts):
- Confere
hub.mode === "subscribe"ehub.verify_token === WHATSAPP_VERIFY_TOKEN - Em caso positivo, responde
200com ohub.challengecru (text/plain) - Caso contrário,
403
No painel da Meta, o Verify token precisa ser idêntico ao WHATSAPP_VERIFY_TOKEN do ambiente. Sem isso, a validação do callback falha com "The callback URL or verify token couldn't be validated".
Webhook de Entrada (POST /webhook)
Payload (MetaWebhookPayload)
Arquivo: src/modules/gateway/adapters/meta/meta.types.ts
typescript
interface MetaWebhookPayload {
object?: string // 'whatsapp_business_account'
entry?: {
id?: string
changes?: {
field?: string
value?: {
messaging_product?: string // 'whatsapp'
messages?: MetaMessage[] // mensagens recebidas
statuses?: unknown[] // recibos de entrega/leitura (ignorados)
}
}[]
}[]
}Cada MetaMessage traz id, from (telefone, sem +), timestamp (unix em segundos), type e o conteúdo (text.body, image.caption, video.caption, ...).
Detecção de provedor
isMetaPayload (src/modules/gateway/helpers/is-meta-payload.ts) retorna true quando body.object === "whatsapp_business_account". O InboundWebhookHandler usa isso para escolher o mapper:
- Meta →
mapMetaPayloadToIncomingMessage - Z-API →
mapWebhookPayloadToIncomingMessage
Mapeamento (mapMetaPayloadToIncomingMessage)
Arquivo: src/modules/gateway/adapters/meta/meta.mapper.ts. Produz o mesmo IncomingMessage que o Z-API, com provider: "meta". Retorna null (webhook ignorado com 200) quando:
- não há
entry[0].changes[0].value.messages[0](ex.: webhook só destatuses) - a mensagem não tem
idoufrom
Limitação atual: apenas a primeira mensagem do batch é processada. A Meta pode agrupar várias mensagens num único webhook.
Envio de Mensagens (MetaClient)
Arquivo: src/modules/gateway/adapters/meta/meta.client.ts. Implementa MessagingClient (mesma interface do ZapiClient).
POST {META_GRAPH_BASE_URL}/{META_GRAPH_VERSION}/{META_PHONE_NUMBER_ID}/messages
Authorization: Bearer {META_ACCESS_TOKEN}
{ "messaging_product": "whatsapp", "recipient_type": "individual",
"to": "<phone>", "type": "text", "text": { "body": "<msg>", "preview_url": false } }A resposta ({ messages: [{ id }] }) vira SentMessageReceipt { messageId }. Falha de rede → BotError 502.
Roteamento por Provedor (MessagingGateway)
Arquivo: src/modules/gateway/services/messaging-gateway.service.ts. É o MessagingClient injetado em todos os flows/handlers no lugar do client concreto.
- No inbound, o handler chama
rememberProvider(phone, incoming.provider) - No
sendText, o gateway resolve o provedor pelo telefone (mapaphone → provider) e delega ao client certo - Telefone sem provedor lembrado cai no default (
"zapi"), o que mantém o envio proativo (/outbound) compatível
Limitação atual: o mapa
phone → provideré em memória (uma instância, reseta no restart). A resposta síncrona à mensagem recebida sempre acerta o provedor; o envio proativo usa o default. Persistir o provedor na sessão é um follow-up.
Pendências / Follow-ups
- Verificação de assinatura (
X-Hub-Signature-256comMETA_APP_SECRET) ainda não implementada — oPOST /webhookda Meta não é autenticado (mesmo patamar do Z-API hoje) - Processar todas as mensagens de um batch
- Persistir
phone → provider(sessão) para roteamento proativo correto
Documento criado em Junho 2026