Voltar pro blog json-p.org

Pilar1 · Infra de WhatsApp que não cai Publicado22 de agosto de 2026 AutorElton

Monitorar a Evolution API antes do cliente reclamar

Como monitorar a Evolution API e saber que caiu antes do cliente reclamar: três camadas de checagem (processo, sessão e entrega real), com código.

São 8h40. Você abre o celular e tem três mensagens: "oi, mandei o comprovante ontem à noite e ninguém respondeu", "o link não chegou" e um cliente irritado perguntando se você sumiu. Você entra no manager da Evolution, olha a instância e ela está com status close. Caiu às 3h da manhã. Cinco horas e meia de follow-up indo pro vazio, e quem te avisou foi o cliente.

O detalhe que dói: você até tinha monitoramento. Um Uptime Kuma pingando https://sua-evolution.seudominio.com de cinco em cinco minutos, tudo verde a noite inteira. E estava verde mesmo, porque o container nunca morreu. O que morreu foi a sessão do WhatsApp dentro dele, e o ping no host não tem como saber disso. O processo continuou respondendo 200 com a maior cara de paisagem enquanto a instância estava desconectada.

Essa é a diferença entre monitorar um servidor e monitorar um funil de vendas. O servidor pode estar perfeito e o funil parado. Se você não separa essas coisas, seu painel de monitoramento vira decoração: bonito, verde, inútil.

A resposta curta: monitorar Evolution API que caiu são três checagens, não uma

"Caiu" é uma palavra que esconde três falhas bem diferentes, e cada uma precisa de um teste próprio:

  1. O processo morreu. Container parado, VPS sem memória, Traefik sem rota. A API não responde nada. Um ping HTTP simples pega isso.
  2. A sessão caiu. A API responde 200 lindamente, mas a instância está em close ou connecting. O WhatsApp derrubou o pareamento, o celular ficou dias offline, alguém escaneou o número em outro lugar. Só uma checagem por instância pega isso.
  3. Está tudo "aberto" e nada sai. Estado open, API viva, e mesmo assim mensagem não é entregue e webhook não chega. Socket zumbi, fila travada, número bloqueado. Só um teste de ponta a ponta pega isso.

A maioria dos tutoriais para na camada 1 e chama de monitoramento. O resto do post é como montar as três, com o endpoint de cada uma, o payload real e o cuidado pra não criar um alerta que você vai aprender a ignorar em duas semanas.

Camada 1: o processo está de pé?

A raiz da Evolution API responde sem autenticação nenhuma. É o teste mais barato que existe:

curl -s https://sua-evolution.seudominio.com/

Resposta esperada:

{
  "status": 200,
  "message": "Welcome to the Evolution API, it is working!",
  "version": "2.1.0",
  "clientName": "seu-cliente",
  "manager": "https://sua-evolution.seudominio.com/manager",
  "documentation": "https://doc.evolution-api.com"
}

Isso é o que você joga num Uptime Kuma, num healthcheck de container ou num monitor externo. Vale monitorar de fora da sua rede, não de dentro do mesmo servidor: se a VPS inteira cair, um monitor rodando nela cai junto e ninguém te avisa.

Um detalhe pequeno que ajuda depois: guarde o campo version. Quando a instância começa a se comportar diferente do nada, olhar o histórico e ver que a versão mudou naquele dia economiza duas horas de investigação.

Essa camada é necessária e completamente insuficiente. Ela responde "o software está rodando", não "o WhatsApp está conectado". No caso das 3h da manhã lá de cima, ela estaria verde do começo ao fim.

Camada 2: a sessão está conectada?

Aqui mora o monitoramento que realmente importa. A Evolution expõe o estado de cada instância:

curl -s -H "apikey: $EVOLUTION_APIKEY" \
  https://sua-evolution.seudominio.com/instance/connectionState/vendas-01

Resposta de uma instância saudável:

{ "instance": { "instanceName": "vendas-01", "state": "open" } }

Os três estados que você vai encontrar na prática:

Se você tem várias instâncias (e quem vende sério sempre acaba tendo), varrer uma a uma no cURL fica chato. O fetchInstances devolve todas de uma vez:

curl -s -H "apikey: $EVOLUTION_APIKEY" \
  https://sua-evolution.seudominio.com/instance/fetchInstances \
  | jq -r '.[] | "\(.name // .instance.instanceName)\t\(.connectionStatus // .instance.state)"'

Repare no // do jq: o formato dessa resposta mudou entre versões. Na v1 os campos vinham aninhados dentro de instance; na v2 eles vêm mais rasos, com name e connectionStatus. Se o seu monitor quebrar depois de um update, é quase sempre aqui. Escrever o parser tolerante aos dois formatos custa dez segundos e evita um falso negativo silencioso, que é o pior tipo de falha de monitoramento: o monitor acha que está tudo bem porque não conseguiu ler nada.

Alerta com histerese, pra não virar ruído

Não alerte na primeira leitura ruim. Reconexão curta acontece o tempo todo, e um alerta por flap treina você a ignorar alerta. A regra que funciona: checa de minuto em minuto, alerta só depois de N leituras consecutivas fora de open, e alerta uma vez, não a cada minuto.

#!/usr/bin/env bash
# checa-instancia.sh, roda a cada minuto no cron
INSTANCIA="vendas-01"
CONTADOR="/var/lib/evo-monitor/$INSTANCIA.falhas"
LIMITE=3

estado=$(curl -s --max-time 10 -H "apikey: $EVOLUTION_APIKEY" \
  "$EVOLUTION_URL/instance/connectionState/$INSTANCIA" \
  | jq -r '.instance.state // "sem_resposta"')

if [ "$estado" = "open" ]; then
  echo 0 > "$CONTADOR"
  exit 0
fi

falhas=$(( $(cat "$CONTADOR" 2>/dev/null || echo 0) + 1 ))
echo "$falhas" > "$CONTADOR"

# -eq e nao -ge: dispara uma vez so, no momento em que cruza o limite
if [ "$falhas" -eq "$LIMITE" ]; then
  ./alerta.sh "instancia $INSTANCIA em '$estado' ha $LIMITE checagens"
fi

Com LIMITE=3 e cron de um minuto, você é avisado em até 3 minutos de queda real e não é incomodado por reconexão de 40 segundos. Compare com as cinco horas e meia da abertura. O número honesto aqui é: 3 minutos de janela cega, não zero. Zero não existe em polling.

Melhor que perguntar: deixar a API avisar

Polling tem latência por construção. A Evolution manda um evento de webhook toda vez que o estado muda, o CONNECTION_UPDATE, e ele chega em segundos:

{
  "event": "CONNECTION_UPDATE",
  "instance": "vendas-01",
  "data": {
    "state": "close",
    "statusReason": 401
  },
  "date_time": "2026-08-20T03:14:22.145Z",
  "server_url": "https://sua-evolution.seudominio.com"
}

Dois avisos de campo. Primeiro: dependendo da versão e da configuração, o nome do evento chega como CONNECTION_UPDATE ou como connection.update, e o motivo aparece ora em statusReason, ora em statusCode. Trate os dois nomes, senão seu handler ignora o evento mais importante que você vai receber. Segundo: se você usa WEBHOOK_BY_EVENTS, esse evento vai pra /webhook/connection-update, não pra URL base. Se você configurou webhook e nunca viu esse evento chegar, vale conferir isso antes de qualquer outra coisa, junto com o roteiro do post sobre webhook do WhatsApp que não dispara.

E o ponto crítico: webhook cobre queda de sessão, mas não cobre queda de servidor. Se o container morre, ninguém manda evento nenhum, e silêncio parece paz. Por isso push e polling não são alternativas, são complementares: o webhook te dá a notícia rápida, o polling te dá a garantia de que a ausência de notícia significa alguma coisa.

Traduzir o motivo: o número dentro do statusReason

Aquele 401 do payload não é um erro HTTP da Evolution. É o código de desconexão do Baileys, a biblioteca que fala o protocolo do WhatsApp Web por baixo. Cada número pede uma ação diferente, e é isso que transforma "caiu" em algo acionável:

Com esse mapa na mão, o alerta deixa de ser "instância vendas-01 caiu" e vira "vendas-01 caiu com 401, vai precisar de QR novo, ninguém vai reconectar isso sozinho". A primeira mensagem te tira do sério; a segunda te diz se dá pra terminar o café antes de agir.

Dois códigos merecem atenção especial. O 440 quase sempre significa que você deixou duas instâncias apontando pro mesmo número (uma antiga esquecida em outro servidor, por exemplo) e elas ficam se derrubando em revezamento. O 403 é o que você não quer ver: é sinal de que o problema não é infra, é a conta.

Camada 3: o canário, pra pegar a instância zumbi

O estado open mente às vezes. A instância aparece conectada, o painel está verde, e mesmo assim mensagem não sai ou evento não chega. Acontece quando o socket ficou pendurado, quando a fila interna travou ou quando o número está com alguma restrição do lado do WhatsApp. Nesse cenário as camadas 1 e 2 dizem que está tudo bem, e está tudo parado.

A única checagem que não mente é usar o caminho de verdade. Um canário: a cada 30 minutos, sua instância manda uma mensagem curta pra um número de controle (um segundo chip seu, ou a conversa com você mesmo) e o monitor confirma que o evento correspondente voltou pelo webhook dentro de, digamos, 60 segundos. Não voltou, alerta.

Isso são 48 mensagens por dia, volume irrelevante, e cobre exatamente o buraco que ninguém cobre. Duas precauções: mande sempre pro mesmo número de controle e num intervalo folgado, porque canário agressivo (a cada minuto, pra números variados) é justamente o tipo de padrão mecânico que pesa contra você, como discuti em como o WhatsApp detecta disparo. E não use o canário como único monitor: ele é lento por natureza e serve pra confirmar entrega, não pra detectar queda rápido.

Se você não quiser gastar mensagem nenhuma, existe uma versão mais fraca: monitorar a última vez que qualquer evento chegou no seu webhook e alertar se passar muito tempo sem nada. Funciona bem em instância com movimento constante e gera falso positivo em instância de baixo volume, onde silêncio pode ser só madrugada de terça. Escolha conforme o seu tráfego, sabendo que essa versão é palpite e a do canário é prova.

Onde o alerta chega (e quem monitora o monitor)

Regra que parece óbvia e que muita gente descobre do jeito difícil: o alerta de que o WhatsApp caiu não pode ser enviado pelo WhatsApp que caiu. Se seu único canal de alerta é a própria instância, o dia que ela morrer você não recebe nada e vai achar que está tudo bem. Telegram, e-mail, ntfy, push, qualquer coisa fora daquele caminho. Se quiser mesmo receber no WhatsApp, use uma segunda instância, em outro host e outro número, e mantenha um canal de reserva.

O segundo detalhe é o silêncio. Um monitor que morre não reclama. Se o seu cron parar (disco cheio, container do monitor reiniciado, token expirado), você fica sem alerta e sem saber. A solução é um dead man's switch: o script bate numa URL de push a cada rodada bem sucedida, e o serviço externo alerta se essa batida parar de chegar. Uptime Kuma tem monitor de push nativo pra isso, e serviços de cron monitoring fazem a mesma coisa. Uma linha no fim do script:

curl -s -m 5 -o /dev/null "$URL_PUSH_MONITOR" || true

Esse é o ponto em que monitoramento deixa de ser "um ping" e vira sistema: camada de processo, camada de sessão com histerese, canário de entrega, alerta fora do canal e um vigia pro vigia. Sim, é mais trabalho do que colar uma URL no Uptime Kuma. É também a diferença entre descobrir em 3 minutos e descobrir às 8h40 pela boca do cliente.

Essa mesma pilha de três camadas é o que roda por trás do whatspix, o funil de WhatsApp que eu mantenho, com uma diferença de enquadramento: lá o estado da instância é tratado como dado de negócio, não como métrica de infra, porque instância em close significa régua de venda parada e cliente esfriando. Mas nada do que está acima depende dele. É curl, cron, jq e um handler de webhook.

O que dá errado

Fechando

Monitorar a Evolution API é aceitar que "caiu" são três perguntas diferentes: o processo está vivo, a sessão está conectada, e a mensagem realmente sai. Responda as três, alerte com histerese, traduza o código de desconexão pra uma ação concreta e mande o aviso por um canal que não depende do que quebrou. Não é elegante nem bonito de mostrar. É o que faz você abrir o celular às 8h40 sem susto, porque o susto já chegou às 3h05, com o motivo escrito, enquanto ainda dava pra resolver.

Fontes primárias consultadas em 22/08/2026: documentação da Evolution API (endpoint raiz, connectionState, fetchInstances e eventos de webhook, incluindo CONNECTION_UPDATE e o roteamento por WEBHOOK_BY_EVENTS), documentação e código do Baileys (enum DisconnectReason e seus valores numéricos) e issues do repositório da Evolution API sobre instâncias presas em connecting consumindo recursos.