WAHA_INTEGRATION.md

šŸ“± WAHA Integration — Manual Tecnico

Public Markdown Document

šŸ“± WAHA Integration — Manual Tecnico

Camada de infraestrutura para envio/recebimento de mensagens WhatsApp via WAHA (https://waha.devlike.pro/). Vive em server/utils/waha.ts + server/api/waha/ — fora do dominio de metadados (App.objects).

560 palavras15 headingsrepository root

šŸ“± WAHA Integration — Manual Tecnico

Camada de infraestrutura para envio/recebimento de mensagens WhatsApp via WAHA (https://waha.devlike.pro/).
Vive em server/utils/waha.ts + server/api/waha/* — fora do dominio de metadados (App.objects).


1. Posicionamento Arquitetural

WAHA e Infrastructure Layer, nao Metadata Layer. Segue o mesmo padrao de Prisma, Socket.IO e do fastapp-agent:

graph TB
    subgraph "Metadata (App JSON)"
        OBJ["app.objects[]<br/>(comunicado, lead, ...)"]
        WF["app.workflows[]"]
    end
    subgraph "Engine"
        DB["dynamic-db.ts"]
        FADAP["FADAP Protocol"]
    end
    subgraph "Infrastructure"
        WAHA["server/utils/waha.ts<br/>+ server/api/waha/*"]
        AGENT["fastapp-agent.post.ts"]
        SOCKET["socket.ts"]
    end
    OBJ --> DB
    WF -->|trigger| WAHA
    OBJ -.referencia.-> WAHA

    style WAHA fill:#25D366,color:#000
    style OBJ fill:#0891b2,color:#fff

A futura feature de "Painel de Comunicados" sera um Object normal em app.objects[] (ex: comunicado com properties mensagem, target_object, scheduled_at). Quando um comunicado for disparado, o handler chama os endpoints /api/waha/messages/* desta camada.


2. Configuracao de Ambiente

.env:

WAHA_BASE_URL=http://localhost:3000
WAHA_API_KEY=
WAHA_WEBHOOK_TOKEN=
Variavel Obrigatorio Descricao
WAHA_BASE_URL Sim URL do servidor WAHA. Self-hosted Docker default http://localhost:3000.
WAHA_API_KEY Recomendado Header X-Api-Key enviado em toda chamada. Configure tambem no servidor WAHA via WHATSAPP_API_KEY.
WAHA_WEBHOOK_TOKEN Recomendado Token compartilhado para validar webhooks recebidos. Configure no painel WAHA ao definir o hook URL.

Mapeado em nuxt.config.ts como runtimeConfig.waha.* — server-only, nunca exposto ao cliente.

Subir o servidor WAHA (Docker, dev)

docker run -it --rm \
  -p 3000:3000/tcp \
  -e WHATSAPP_API_KEY=sua-chave-aqui \
  --name waha \
  devlikeapro/waha

Acesse http://localhost:3000/dashboard para criar a primeira session.


3. Camadas

server/
ā”œā”€ā”€ utils/
│   └── waha.ts                              # Wrapper HTTP + tipos + helpers
└── api/
    └── waha/
        ā”œā”€ā”€ sessions/
        │   ā”œā”€ā”€ index.get.ts                 # GET    listar sessions
        │   ā”œā”€ā”€ index.post.ts                # POST   criar/iniciar session
        │   ā”œā”€ā”€ [name].get.ts                # GET    status detalhado
        │   ā”œā”€ā”€ [name].delete.ts             # DELETE logout (apaga creds)
        │   └── [name]/
        │       ā”œā”€ā”€ stop.post.ts             # POST   para session
        │       └── qr.get.ts                # GET    QR code para parear
        ā”œā”€ā”€ messages/
        │   ā”œā”€ā”€ text.post.ts                 # POST   enviar texto
        │   └── image.post.ts                # POST   enviar imagem
        ā”œā”€ā”€ check-number.get.ts              # GET    validar se numero existe no WhatsApp
        └── webhook.post.ts                  # POST   recebe eventos do servidor WAHA

Funcoes do wrapper (server/utils/waha.ts)

Funcao Descricao
waha.listSessions() Lista todas as sessions do servidor
waha.getSession(name) Status detalhado de uma session
waha.startSession(name, config?) Cria/inicia session (proximo passo: ler QR)
waha.stopSession(name) Para session (mantem credenciais)
waha.logoutSession(name) Logout completo (apaga credenciais)
waha.getQr(name, format?) QR code para parear (image base64 PNG ou raw)
waha.sendText(session, payload) Envia texto
waha.sendImage(session, payload) Envia imagem (URL ou base64)
waha.sendFile(session, payload) Envia arquivo generico
waha.checkNumber(session, phone) Valida se numero tem WhatsApp
phoneToChatId(phone, cc?) Helper: "11999999999" -> "[email protected]"
isWahaConfigured() Verifica se ambiente esta minimamente setado

4. Endpoints Publicos (proxy)

Todos os endpoints sao server-side proxies — a API key WAHA nunca sai do servidor.

Sessions

Rota Metodo Body / Query Retorno
/api/waha/sessions GET — WahaSession[]
/api/waha/sessions POST { name, config? } WahaSession (status inicial)
/api/waha/sessions/:name GET — WahaSession
/api/waha/sessions/:name DELETE — logout
/api/waha/sessions/:name/stop POST — para session
/api/waha/sessions/:name/qr GET ?format=image|raw QR code

Mensagens

Rota Metodo Body Notas
/api/waha/messages/text POST { session, chatId? | phone?, text, linkPreview?, reply_to? } Pelo menos um entre chatId e phone
/api/waha/messages/image POST { session, chatId? | phone?, file: { url } | { mimetype, filename, data }, caption? } Aceita URL publica ou base64
/api/waha/check-number GET ?session=&phone= Util para limpar listas antes de campanhas

Webhook

Rota Metodo Descricao
/api/waha/webhook POST Receptor de eventos (mensagens recebidas, ack, status). Validado via ?token= ou header X-Webhook-Token.

5. Ciclo de Pareamento (primeira vez)

sequenceDiagram
    participant Admin
    participant API as /api/waha
    participant WAHA as Servidor WAHA
    participant WhatsApp

    Admin->>API: POST /sessions { name: "default" }
    API->>WAHA: POST /api/sessions
    WAHA-->>API: { status: "STARTING" }
    API-->>Admin: status

    loop ate WORKING
        Admin->>API: GET /sessions/default/qr
        API->>WAHA: GET /api/default/auth/qr
        WAHA-->>API: PNG base64
        API-->>Admin: QR code
        Admin->>WhatsApp: escaneia
        WhatsApp->>WAHA: pareado
        Admin->>API: GET /sessions/default
        API-->>Admin: { status: "WORKING" }
    end

6. Exemplo de Envio (uso futuro pelo Painel de Comunicados)

// Server-side handler do "comunicado", em segunda fase:
await $fetch('/api/waha/messages/text', {
    method: 'POST',
    body: {
        session: 'default',
        phone: lead.whatsapp,           // sera normalizado via phoneToChatId
        text: comunicado.mensagem
    }
});

7. Como o "Painel de Comunicados" vai usar isso (segunda fase)

A feature sera construida dentro do padrao Fastapp:

  1. Object comunicado em app.objects[] com properties:

    • titulo (string), mensagem (string, multiline)
    • target_object (string — qual entity recebe, ex: "lead")
    • target_filter (json — filtros tipo { status: "ATIVO" })
    • channel (enum: whatsapp | email) — preparado para multi-canal
    • waha_session (string — qual session usar)
    • scheduled_at (datetime)
    • status (enum: draft | scheduled | sending | sent | failed)
  2. Page comunicados em app.pages[] (CRUD dinamico, gerado pelo motor)

  3. Workflow opcional para aprovacao multi-step

  4. Disparador (server handler novo, ex: /api/comunicado/[id]/dispatch.post.ts):

    • Le o objeto comunicado
    • Resolve a lista de leads pelo target_filter
    • Para cada lead: waha.sendText(session, { chatId: phoneToChatId(lead.whatsapp), text: comunicado.mensagem })
    • Atualiza status e gera log por destinatario

A camada WAHA atual nao precisa mudar para isso ser feito — ela ja expoe tudo que e necessario.


8. Eventos do Webhook (referencia)

WAHA empurra eventos como:

Evento Disparo
message Mensagem recebida (texto, midia, etc)
message.ack Ack de entrega/leitura
session.status Mudanca de estado da session
state.change Mudanca de estado WhatsApp
group.join / group.leave Participacao em grupo

Configure no WAHA via env vars do container:

WHATSAPP_HOOK_URL=https://creator.fastapp.cloud/api/waha/webhook?token=SEU_WEBHOOK_TOKEN
WHATSAPP_HOOK_EVENTS=message,message.ack,session.status

A logica de roteamento de eventos e um TODO da segunda fase — o handler atual valida o token e loga.


9. Limitacoes Conhecidas

  • Esta camada nao persiste mensagens enviadas/recebidas. A persistencia (historico, threads) sera feita pelo Object comunicado na segunda fase.
  • Sem rate limiting proprio — confie no rate limit do servidor WAHA + cuidado com WhatsApp ban (recomendado: ~1 msg/segundo por session).
  • phoneToChatId assume Brasil (+55) por padrao — passe o segundo arg para outro DDI.

Status: Infraestrutura pronta. Painel de Comunicados (segunda fase) ainda nao implementado.
Branch: feat/Waha