Appearance
Integração Z-API
Contrato HTTP do Z-API: payload do webhook, envio de mensagens e mapeamento para o domínio.
Visão Geral
O Z-API (z-api.io) é o gateway entre o WhatsApp e nosso Lambda. Ele:
- Recebe mensagens da instância de WhatsApp e envia para
POST /webhookdo nosso API Gateway - Envia mensagens via API REST (
POST /send-text) quando chamamos oZapiClient
A instância Z-API é configurada no painel próprio do serviço (não em CDK). As três credenciais ficam em variáveis de ambiente do Lambda: ZAPI_INSTANCE_ID, ZAPI_TOKEN, ZAPI_CLIENT_TOKEN.
Webhook de Entrada
Payload (ZapiWebhookPayload)
Arquivo: src/adapters/zapi/zapi.types.ts
typescript
interface ZapiWebhookPayload {
messageId?: string
phone?: string
fromMe?: boolean
type?: string // 'text' | 'image' | 'audio' | 'video' | 'document' | outros
momment?: number // unix timestamp (s ou ms)
text?: { message?: string }
image?: { caption?: string; imageUrl?: string }
audio?: { audioUrl?: string }
video?: { caption?: string; videoUrl?: string }
document?: { documentUrl?: string; fileName?: string }
[key: string]: unknown // tolerância a campos extras
}Campos descritos por documentação do Z-API. Note que momment (com dois M) é grafia oficial deles, preservamos a tipagem para evitar surpresa.
Mapper para o Domínio
Arquivo: src/adapters/zapi/zapi.mapper.ts
O mapper traduz o payload externo em IncomingMessage interno:
typescript
interface IncomingMessage {
messageId: string
phone: string // somente dígitos (normalizado)
fromMe: boolean
type: 'text' | 'image' | 'audio' | 'video' | 'document' | 'unknown'
text: string | null // text.message → image.caption → video.caption → null
receivedAt: Date
raw: Record<string, unknown> // payload original (debug/audit)
}Regras do mapper:
| Regra | Comportamento |
|---|---|
Sem messageId ou sem phone | Devolve null handler ignora a mensagem |
phone com máscara | Strip de tudo que não é dígito (via normalizePhone) |
type ausente | Vira 'unknown' |
type em maiúsculas/mistas | Normalizado para lowercase |
type fora da lista suportada | Vira 'unknown' |
| Texto presente | text.message (com trim) tem prioridade |
| Sem texto, mas com imagem/vídeo | Usa image.caption ou video.caption (com trim) |
| Sem texto algum | text = null |
momment em segundos (< 1e12) | Multiplica por 1000 para virar ms |
momment em milissegundos (≥ 1e12) | Usa direto |
momment ausente/NaN/Infinity | receivedAt = new Date() |
Envio de Mensagens
Cliente HTTP
Arquivo: src/adapters/zapi/zapi.client.ts
typescript
new ZapiClient({
instanceId: process.env.ZAPI_INSTANCE_ID,
token: process.env.ZAPI_TOKEN,
clientToken: process.env.ZAPI_CLIENT_TOKEN,
baseUrl: process.env.ZAPI_BASE_URL ?? 'https://api.z-api.io'
})Configuração do axios.create:
| Propriedade | Valor |
|---|---|
baseURL | {baseUrl}/instances/{instanceId}/token/{token} |
timeout | 10_000 ms |
Header Content-Type | application/json |
Header Client-Token | {clientToken} (omitido se vazio) |
Método sendText
typescript
zapi.sendText({ phone: '5511999998888', message: 'olá' })Endpoint chamado: POST /send-text
Body enviado:
json
{ "phone": "5511999998888", "message": "olá" }Response esperada do Z-API:
json
{ "zaapId": "...", "messageId": "..." }Em caso de falha (network, 5xx, timeout), o client converte em BotError 502 com mensagem genérica "Falha ao enviar mensagem via Z-API": detalhes do upstream não vazam para o usuário final. O detalhe real fica nos logs (Logger.warn).
Configuração no Painel Z-API
Após o deploy, configure o webhook no painel do Z-API apontando para o output BotApiUrl da stack CDK:
https://{api-id}.execute-api.us-east-2.amazonaws.com/webhookTipo do webhook: "ao receber mensagem" (não habilitar webhooks de status/delivery, não consumimos esses eventos).
Documento atualizado em Maio 2026