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:
- O processo morreu. Container parado, VPS sem memória, Traefik sem rota. A API não responde nada. Um ping HTTP simples pega isso.
- A sessão caiu. A API responde 200 lindamente, mas a instância está em
closeouconnecting. 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. - 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:
open: conectada. É o único estado bom.connecting: tentando parear ou reconectando. Normal por alguns segundos, sintoma sério se ficar preso ali por minutos.close: desconectada. Nada entra, nada sai.
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:
401loggedOut: sessão desconectada ou removida no celular. Reconectar não resolve, precisa de QR novo.403forbidden: acesso negado pelo servidor do WhatsApp. É aqui que costuma cheirar a bloqueio do número.408connectionLost e timedOut: perda de rede. Reconecta sozinha na maioria das vezes.411multideviceMismatch: incompatibilidade de multi-device, quase sempre versão de biblioteca velha.428connectionClosed: fechamento normal. Reconectar é seguro.440connectionReplaced: outra sessão assumiu o mesmo número. Duas instâncias brigando pela mesma conta.500badSession: sessão corrompida. Costuma exigir novo pareamento.503unavailableService: serviço indisponível do lado deles. Esperar e tentar depois.515restartRequired: o servidor pediu restart. Reconectar na hora (é comum logo depois de parear).
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
- Achar que ping no host é monitoramento. É a camada 1 e só. Ela ficou verde durante as cinco horas e meia da abertura. Se o seu painel só tem isso, você não monitora a Evolution API, monitora o nginx na frente dela.
- Alertar em todo flap. Sem histerese, você recebe alerta por reconexão de 30 segundos, e em duas semanas silencia o grupo de alertas. Aí o alerta que importava chega e ninguém lê. Fadiga de alerta mata mais monitoramento do que bug.
- Reconectar em loop no 401. Quando a desconexão é
loggedOut, reconectar não vai funcionar por definição, e a tentativa infinita gera instância presa emconnectingconsumindo CPU e memória do servidor (é uma queixa recorrente nas issues do projeto). No 401 o passo certo é parar de tentar e pedir QR novo. - Monitorar a Evolution e esquecer do seu lado. Instância
open, evento saindo, e o seu endpoint que recebe o webhook está retornando 500 desde ontem. A venda se perde igual. Monitore também a taxa de erro do seu receptor e o tamanho da fila de reprocessamento, não só o lado de lá. - Canário caro demais. Teste de entrega de minuto em minuto, pra números diferentes, é você mesmo criando um padrão de disparo automatizado com a sua própria conta. Intervalo folgado e número fixo.
- Achar que trocar de arquitetura resolve tudo. Na API oficial da Meta não existe sessão pra cair, então a camada 2 some. Mas aparecem outros modos de falha pra monitorar (qualidade do número, limite de envio, template rejeitado, erro por janela de 24h fechada), e a camada 3 continua valendo. É troca de problema, não ausência de problema. O comparativo está em Evolution API vs Cloud API.
- Não guardar histórico. Se você só alerta e não registra, nunca vai responder "essa instância cai toda quinta de madrugada?" nem "desde qual versão isso piorou?". Um log simples de cada mudança de estado, com timestamp e código, resolve, e é o que transforma apagar incêndio em corrigir causa.
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.