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).
š± WAHA Integration ā Manual Tecnico
Camada de infraestrutura para envio/recebimento de mensagens WhatsApp via WAHA (https://waha.devlike.pro/).
Vive emserver/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:
Object
comunicadoemapp.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-canalwaha_session(string ā qual session usar)scheduled_at(datetime)status(enum:draft|scheduled|sending|sent|failed)
Page
comunicadosemapp.pages[](CRUD dinamico, gerado pelo motor)Workflow opcional para aprovacao multi-step
Disparador (server handler novo, ex:
/api/comunicado/[id]/dispatch.post.ts):- Le o objeto
comunicado - Resolve a lista de
leads pelotarget_filter - Para cada lead:
waha.sendText(session, { chatId: phoneToChatId(lead.whatsapp), text: comunicado.mensagem }) - Atualiza
statuse gera log por destinatario
- Le o objeto
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
comunicadona segunda fase. - Sem rate limiting proprio ā confie no rate limit do servidor WAHA + cuidado com WhatsApp ban (recomendado: ~1 msg/segundo por session).
phoneToChatIdassume 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