Saúde e heartbeat
Como saber se o lado WhatsApp está de pé — endpoints de saúde, sinais de vida, o que o backoffice mostra e onde procurar nos logs.
Esta página trata da saúde da infraestrutura (processos, filas, sessões). O backoffice segue a regra de AD-016: mostra estado, nunca conteúdo. Nada aqui expõe texto de mensagem, nome ou foto de contato; a identidade da pessoa protegida aparece mascarada.
Endpoints de saúde
| Processo | Rota | O que checa | Resposta |
|---|---|---|---|
| api | GET /health | Postgres e Redis | JSON sem envelope, status: ok (200) ou degraded (503), uptime e ms por serviço |
| gateway | GET /health (porta 3002) | Redis | ok (200) ou degraded (503) |
| WAHA | GET /health com X-Api-Key | o próprio WAHA | usado pelo healthcheck do compose (dev a cada 10 s, prod a cada 15 s) |
| workers | — | — | sem rota HTTP; no boot aborta se Postgres ou Redis não respondem |
curl -s http://localhost:3000/health | jq
curl -s http://localhost:3002/health | jq
curl -fsS -H "X-Api-Key: <WAHA_API_KEY>" http://localhost:3001/health
docker compose ps wahaEm produção o WAHA e o gateway ficam só na rede interna do compose (expose),
sem porta pública; o /health da api é o que o orquestrador lê.
Em aberto
O /health da api não inclui o WAHA, e os workers não têm endpoint de saúde.
Um worker travado ou um WAHA fora do ar não aparecem em nenhum /health do
backend.
Sinais de vida que existem
| Sinal | Onde | Frequência |
|---|---|---|
ping no SSE da conta | GET /accounts/{uid}/events | a cada 5 s |
ping no SSE público do pareamento | GET /consent-requests/{token}/events | a cada 5 s |
channel_syncs.heartbeat_at | importação de histórico | a cada passo da importação |
pairings.last_seen_at | pareamento | só quando a sessão conecta ou reconecta (paired) |
session.status do WAHA | webhook → gateway → workers | quando o WAHA muda o estado da sessão |
Os pings só mantêm a conexão SSE viva. O estado de cada sessão depende dos webhooks: o nosso lado não pergunta ao WAHA de tempos em tempos.
Em aberto
Não há heartbeat por sessão nem reconciliação periódica com o WAHA:
ChannelAdapter.state() existe, mas nada o chama. Se um webhook se perder, o
pareamento fica com o último estado conhecido. last_seen_at não avança
enquanto a sessão segue conectada.
Estado de uma sessão
-
No nosso banco:
pairings.status(ver Pareamento),failure_reason,reconnect_attempts,next_retry_at,provider_worker. -
No WAHA: a sessão se chama
sd-<pairingUid>.curl -fsS -H "X-Api-Key: <WAHA_API_KEY>" \ http://localhost:3001/api/sessions/sd-<pairingUid>Localmente também dá para usar o dashboard em
http://localhost:3001/dashboard.
O que o backoffice mostra
| Tela | Rota do backend | Mostra |
|---|---|---|
| Visão geral | GET /admin/overview | contagens agora: pairingsPaired, pairingsDegraded, proxiesActive, proxiesBurned, syncsRunning, syncsWaiting, syncsStuck, fila |
| Clientes → Conexões | GET /admin/connections?status=&channel= | por canal de cada vaga: status do pareamento, pairedAt, lastSeenAt, worker do WAHA, rótulo do proxy, status e etapa da sincronização |
| Detalhe da conta → linha do tempo da vaga | GET /admin/seats/{uid}/timeline | eventos de consentimento, pareamento (provider.failed, reconnect, cancelled…), sincronização e assinatura, só o tipo |
| Infraestrutura → Proxies | GET /admin/proxies, …/{uid}/bindings | ocupação por endereço, status, quem está em cada um |
| Infraestrutura → Compras / Orçamento | /admin/proxy-supply/* | saldo, renovações, validade, tetos |
Operação → Sessões e Infraestrutura → Dispositivos aparecem no menu, desligadas: esperam rotas que ainda não existem.
Scripts: o que cada um checa
| Script | Checa | Não checa |
|---|---|---|
bun run wa:doctor | a conta WhatsApp Business da Meta (número, templates, assinatura do webhook) usada para mandar o link de consentimento; -- <número> envia o template de verdade | sessões WAHA |
bun run verify:probe -- audio <arquivo> "<frase>" | o verificador de evidência (OpenRouter) com arquivos locais | nada do WhatsApp |
bun run pair:qr -- <seat-uid> | grava o QR (ou mostra o código) do pareamento aberto; só dev, lê o banco direto | saúde |
Em aberto
Nenhum script verifica as sessões WAHA (estado, proxy, webhook). Também não há
alerta para operadores: hoje quem é avisado de uma queda é o responsável,
pelas notificações pairing.disconnected, pairing.identity_mismatch e
pairing.reconnect_required.
Logs
Os processos usam pino: legível em dev, JSON em produção (LOG_FORMAT: json).
docker compose logs -f waha
bun run gateway:dev # e bun run workers:dev, cada um no seu terminal| Mensagem | Processo | Significa |
|---|---|---|
waha webhook refused: missing or bad signature | gateway | HMAC ausente ou errado (chave divergente entre WAHA e gateway) |
WAHA_WEBHOOK_HMAC_KEY is required… | gateway | não subiu sem a chave |
provider stream read failed | workers | leitura do Redis falhou |
provider event failed; left pending | workers | handler lançou; a entrada ficou pendente |
reconnect attempts issued / reconnect restart failed | workers | tick de reconexão |
challenge refresh failed / first challenge fetch failed | workers | WAHA não entregou QR/código |
pairing identity mismatch | workers | número conectado não é o da pessoa |
no purge configured: the provider keeps its plaintext copy | workers | a cópia do histórico no waha-db não foi apagada |
redis-cli XLEN provider:waha
redis-cli XINFO GROUPS provider:waha
redis-cli XPENDING provider:waha workersFontes
backend/src/http/routes/system/health.route.ts,backend/apps/gateway/main.ts,backend/apps/workers/{main,consume-provider-events,reconnect-ticker,challenge-refresher}.tsbackend/src/http/routes/events/stream.route.ts,backend/src/http/routes/consents/pairing.route.tsbackend/src/domains/insights/use-cases/{overview,operator-lists,seat-timeline}.ts,backend/src/http/routes/admin/operator-lists.route.tsbackend/src/domains/ingest/use-cases/run-sync.ts,backend/src/domains/ingest/channels/whatsapp/waha-ingest.tsbackend/scripts/{wa-doctor,verify-probe,pair-qr}.ts,backend/docker-compose.yml,backend/docker-compose.prod.ymlapps/backoffice/src/components/area/areas.ts,apps/backoffice/src/components/clients/list-screens.tsx- Vault: AD-016 (estado, nunca conteúdo), AD-010
