WhatsApp: enviar mensagens grátis com a Evolution API
Olá meus Unicórnios! 🦄✨
Se você manda mensagem no WhatsApp pela API oficial, tenho uma notícia que provavelmente vai mexer com a sua conta no fim do mês: a partir de 1º de outubro de 2026, a Meta passa a cobrar por mensagem enviada. Não por conversa: por mensagem. 💸
E não é só uma mudança de nome na fatura. As mensagens utilitárias, que eram gratuitas desde julho de 2025 quando enviadas dentro da janela de 24 horas, voltam a ser cobradas. As mensagens de serviço, aquelas respostas que você manda quando o cliente puxa assunto, também entram na conta, com a mesma tarifa das utilitárias.
A documentação oficial da Meta sobre a mudançaOnde eles explicam o que passa a ser cobrado por mensagem a partir de 1º de outubro de 2026.developers.facebook.comPara quem manda dez mensagens por dia, tudo bem. Para quem tem um sistema que dispara confirmação de pedido, aviso de entrega, lembrete de consulta e código de acesso, a conta muda de patamar, e ela cresce junto com o seu negócio, que é justamente quando dói. 😅
É aí que entra a Evolution API: uma API não oficial, gratuita e de código aberto, que roda no seu servidor e conversa com o WhatsApp Web por baixo dos panos. Você paga a VPS, poucos dólares por mês, e manda quantas mensagens quiser, sem custo por envio.
Este artigo é o passo a passo completo: da VPS Ubuntu recém-comprada, sem nada instalado, até a mensagem saindo pela API. Com as armadilhas que eu encontrei no caminho, e com a conversa honesta sobre o risco, porque essa parte quase ninguém escreve, e ela é a mais importante.
🖥️ O ponto de partida: uma VPS pelada
Usei uma VPS bem modesta de propósito, para provar um ponto: você não precisa de máquina grande para isso. Vou mostrar o consumo real lá no fim do artigo, e ele surpreende.
A minha eu comprei na Contabo, por US$ 5,50 por mês. Foi a conta que fechou melhor para o que eu queria. Veio com Ubuntu 24.04, 8 GB de RAM e 96 GB de disco, que é bem mais do que a Evolution precisa. Serve qualquer provedor, e serve máquina menor também; se você já tem uma VPS parada em algum lugar, ela dá conta.
ContaboOnde eu comprei a VPS deste tutorial, por US$ 5,50 por mês.contabo.comA primeira coisa é entrar por SSH. No Windows dá para usar o PuTTY, mas o próprio terminal já tem ssh embutido faz anos. Abra o PowerShell e mande:
ssh root@SEU_IP_AQUI
Na primeira conexão ele pergunta se você confia na máquina (aquele Are you sure you want to continue connecting?). Responda yes, digite a senha, e você está dentro.
Antes de instalar qualquer coisa, vale conferir onde você pisou:
cat /etc/os-release | head -3
free -h | head -2
df -h / | tail -1
No meu caso:
PRETTY_NAME="Ubuntu 24.04.4 LTS"
NAME="Ubuntu"
VERSION_ID="24.04"
total used free shared buff/cache available
Mem: 7.8Gi 474Mi 7.3Gi 1.0Mi 266Mi 7.3Gi
/dev/sda1 96G 2.2G 94G 3% /
Ubuntu 24.04, 7,8 GB de RAM, 96 GB de disco, e 2,2 GB usados, ou seja, sistema virgem. É desse ponto que a gente parte.
🐳 Instalando o Docker
A Evolution roda em contêiner, então o Docker é a única dependência de verdade. O jeito oficial é o script de conveniência da própria Docker:
apt-get update
curl -fsSL https://get.docker.com -o get-docker.sh
sh get-docker.sh
Esse script detecta a distribuição sozinho, adiciona o repositório oficial e instala tudo, inclusive o Docker Compose já como plugin, que é o que a gente precisa. Confira as duas versões:
docker --version
docker compose version
Docker version 29.7.2, build a7dcaa6
Docker Compose version v5.5.0
Repare que é docker compose com espaço, e não docker-compose com hífen. O hífen é a versão antiga, em Python, que foi descontinuada. Se você encontrar um tutorial mandando instalar `docker-compose` por fora, é sinal de que ele é velho. 🙃
🔑 Gerando as senhas antes de escrever o arquivo
Aqui vai um hábito que eu recomendo muito: não invente senha na mão e, principalmente, não deixe aquela change-me-123 que vem nos exemplos. A Evolution vai ficar exposta na internet, e a chave dela é a única coisa entre o seu WhatsApp e o mundo.
O próprio Ubuntu gera chaves boas. Primeiro, crie a pasta onde tudo vai morar e entre nela:
mkdir -p /opt/evolution
cd /opt/evolution
O mkdir cria a pasta e o cd entra nela. A partir daqui, todo comando do tutorial roda de dentro de /opt/evolution. Se você fechar o terminal e voltar depois, rode o cd de novo antes de continuar.
Agora as duas senhas, geradas com o openssl, que já vem instalado:
openssl rand -hex 32
openssl rand -hex 16
Cada linha cospe uma sequência aleatória de letras e números, assim:
a3f8e1c7b94d2056fa17e3c8d94b2f60e7a15c39d8b246f0ac91e5d73b820f4e
5d8c1a94f7e30b62d5a8c41f9e26b073
A primeira (a maior) é a chave da API; a segunda é a senha do banco. Copie as duas para um bloco de notas: a primeira você vai usar bastante daqui em diante.
📝 Criando arquivo no Ubuntu: o nano
Se você nunca mexeu num servidor Linux, esta é a parte que trava todo mundo: como é que eu crio um arquivo aqui, se não tem Bloco de Notas? 😅
Tem sim, chama-se nano, já vem instalado, e é o mais simples de todos. Você digita nano seguido do nome do arquivo:
nano .env
A tela do terminal vira o editor. Se o arquivo não existir, ele é criado na hora; se existir, abre para edição. Aí é só digitar (ou colar) o conteúdo normalmente.
Para colar, use Ctrl + Shift + V ou clique com o botão direito. O Ctrl + V comum não funciona no terminal, e essa é a primeira coisa que confunde. 🙃
Para salvar e sair, são três teclas em sequência:
Ctrl + O grava o arquivo (a letra O, de "output")
Enter confirma o nome
Ctrl + X sai do editor
Repare no rodapé do nano: ele mostra os atalhos o tempo todo, com o ^ significando Ctrl. Ou seja, ^O é Ctrl + O e ^X é Ctrl + X. Depois de duas ou três vezes vira automático.
🔐 O arquivo .env, com as senhas
Com o nano na mão, crie o primeiro arquivo:
nano .env
E escreva estas duas linhas, trocando pelos valores que o openssl gerou para você:
AUTHENTICATION_API_KEY=cole_aqui_a_chave_de_64_caracteres
POSTGRES_PASSWORD=cole_aqui_a_senha_de_32_caracteres
Salve com Ctrl + O, Enter, Ctrl + X. Para conferir que ficou certo:
cat .env
O cat mostra o conteúdo de um arquivo na tela. Se aparecerem as duas linhas com as suas senhas, está pronto.
Guarde essa chave num lugar seguro: é ela que você vai usar para entrar no painel e em toda chamada da API. E note que ela fica num arquivo .env separado, não dentro do docker-compose.yml: assim você pode mostrar o compose para alguém sem mostrar a senha junto.
📦 O docker-compose.yml
A Evolution v2 precisa de três peças: ela mesma, um PostgreSQL para guardar as mensagens e um Redis para o cache. Dá para rodar sem banco, mas aí você perde o histórico toda vez que reiniciar, e não vale a pena.
Esse arquivo é a receita que descreve as três: qual imagem cada uma usa, quais portas abre e como conversam entre si. Mesma coisa de antes: nano, cola, salva.
nano docker-compose.yml
E o conteúdo é este (troque o SEU_IP_AQUI pelo IP da sua VPS, aquele mesmo do SSH):
services:
evolution-api:
image: evoapicloud/evolution-api:v2.3.7
container_name: evolution-api
restart: always
ports:
- "8080:8080"
environment:
SERVER_URL: http://SEU_IP_AQUI:8080
AUTHENTICATION_API_KEY: ${AUTHENTICATION_API_KEY}
DATABASE_ENABLED: "true"
DATABASE_PROVIDER: postgresql
DATABASE_CONNECTION_URI: postgresql://evolution:${POSTGRES_PASSWORD}@postgres:5432/evolution
DATABASE_SAVE_DATA_INSTANCE: "true"
DATABASE_SAVE_DATA_NEW_MESSAGE: "true"
DATABASE_SAVE_MESSAGE_UPDATE: "true"
DATABASE_SAVE_DATA_CONTACTS: "true"
DATABASE_SAVE_DATA_CHATS: "true"
CACHE_REDIS_ENABLED: "true"
CACHE_REDIS_URI: redis://redis:6379/6
CACHE_REDIS_PREFIX_KEY: evolution
CACHE_LOCAL_ENABLED: "false"
depends_on:
- postgres
- redis
volumes:
- evolution_instances:/evolution/instances
postgres:
image: postgres:15-alpine
container_name: evolution-postgres
restart: always
environment:
POSTGRES_USER: evolution
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: evolution
volumes:
- postgres_data:/var/lib/postgresql/data
redis:
image: redis:7-alpine
container_name: evolution-redis
restart: always
volumes:
- redis_data:/data
volumes:
evolution_instances:
postgres_data:
redis_data:
Salve e saia, como antes. E aqui vai o aviso mais importante para quem está começando:
Antes de subir, dá para perguntar ao próprio Docker se o arquivo está válido. São trinta segundos que evitam muita dor de cabeça:
docker compose config
Se ele imprimir o conteúdo de volta, está tudo certo. Se a indentação estiver errada, você leva um recado assim:
go-yaml load error in scanner at L4.C19: mapping values are not allowed in this context
Traduzindo: ele achou algo fora do lugar na linha 4, coluna 19. A mensagem não diz "faltou um espaço", mas quando esse erro aparecer logo depois de você colar o arquivo, é indentação em 90% das vezes. Confira a linha que ele apontou.
Três coisas nesse arquivo merecem atenção, e uma delas é a armadilha que me custou tempo. Vamos por partes.
💀 A imagem que mudou de nome (e o erro que mente)
Se você só for ler um pedaço deste artigo, leia este. 🙏
Todo tutorial da Evolution que você achar no Google (inclusive o meu antigo) manda usar a imagem atendai/evolution-api. Escrevi exatamente isso na primeira tentativa, e recebi:
Error response from daemon: pull access denied for atendai/evolution-api,
repository does not exist or may require 'docker login'
Repare no que a mensagem sugere: "may require docker login". Ela está te dizendo que talvez seja problema de autenticação. E aí você vai fazer docker login, criar conta no Docker Hub, conferir senha… e nada disso resolve, porque não é esse o problema. 😤
Para confirmar que não era a minha máquina nem a minha rede, testei baixar outra imagem qualquer:
docker pull hello-world
Status: Downloaded newer image for hello-world:latest
docker.io/library/hello-world:latest
Baixou na hora. Ou seja: Docker funciona, rede funciona, DNS funciona. O problema era só com aquele nome.
E a causa é simples quando você descobre: o projeto mudou de organização no Docker Hub. O repositório atendai/evolution-api não existe mais. O endereço novo é:
evoapicloud/evolution-api
É por isso que a mensagem engana tanto. Para o Docker, "repositório que não existe" e "repositório privado que você não pode ver" são indistinguíveis: em ambos os casos ele recebe a mesma negativa do servidor. Então ele junta as duas hipóteses na mesma frase e joga para você decidir. Se ele dissesse "esse repositório não existe", metade da internet teria resolvido isso em dez segundos.
A lição que fica, e vale para qualquer imagem: quando o pull falhar, teste uma imagem que você sabe que existe. Se ela baixar, o problema é o nome, não a sua autenticação.
🚀 Subindo a stack
Com o nome certo no arquivo, é uma linha só:
cd /opt/evolution
docker compose up -d
O -d é de detached: ele sobe e devolve o terminal para você. Na primeira vez demora um pouco, porque precisa baixar tudo. Depois:
docker compose ps
NAME STATUS
evolution-api Up 44 seconds
evolution-postgres Up 44 seconds
evolution-redis Up 44 seconds
Os três de pé. Mas não confie no "Up". Ele diz que o contêiner está rodando, não que a aplicação lá dentro terminou de subir. Quem conta a verdade é o log:
docker logs evolution-api | tail -20
O que você quer ver é esta sequência, na ordem:
[Evolution API] v2.3.7 VERBOSE [Redis] redis ready
[Evolution API] v2.3.7 INFO [PrismaRepository] Repository:Prisma - ON
[Evolution API] v2.3.7 LOG [SERVER] HTTP - ON: 8080
Redis conectado, banco conectado, e por último o servidor HTTP no ar. Se parar antes do HTTP - ON, tem algo errado, e o log vai dizer o quê, geralmente senha do banco que não bate.
Agora o teste de verdade:
curl http://localhost:8080/
{
"status": 200,
"message": "Welcome to the Evolution API, it is working!",
"version": "2.3.7",
"clientName": "evolution_exchange",
"manager": "http://localhost:8080/manager",
"documentation": "https://doc.evolution-api.com"
}
"it is working!" 🎉 A API está de pé.
🌐 A SERVER_URL: o campo que quebra o painel em silêncio
Olhe de novo a resposta ali de cima, no campo manager:
"manager": "http://localhost:8080/manager"
Isso é um problema, e é sutil. Eu tinha deixado SERVER_URL: http://localhost:8080 no compose, e a Evolution acredita em você: ela usa esse valor para montar todos os links que devolve.
Só que localhost, para quem acessa de fora, é o próprio computador de quem acessa. O painel abre no seu navegador e tenta falar com a API na sua máquina, onde não tem API nenhuma. Resultado: tela em branco, QR Code que não aparece, e nenhuma mensagem de erro explicando o motivo. 🙄
A correção é trocar pelo IP público (ou o domínio, se você tiver um). Abra o compose de novo:
nano docker-compose.yml
Ache a linha do SERVER_URL e deixe-a assim, com o IP da sua VPS:
SERVER_URL: http://SEU_IP_AQUI:8080
Salve (Ctrl + O, Enter, Ctrl + X) e mande o Docker aplicar a mudança:
docker compose up -d
Não precisa parar nada antes: o up -d percebe sozinho que o arquivo mudou e recria só o contêiner afetado, deixando o banco e o Redis de pé.
Agora a mesma chamada devolve o endereço certo:
{
"status": 200,
"message": "Welcome to the Evolution API, it is working!",
"version": "2.3.7",
"manager": "http://SEU_IP_AQUI:8080/manager"
}
E tem um detalhe que quase me pegou logo depois. Assim que rodei o docker compose up -d, testei na hora e levei um erro de conexão, e achei que tinha quebrado alguma coisa. Não tinha: recriar o contêiner reinicia a aplicação, e ela leva uns 20 a 30 segundos para regenerar o cliente do banco e voltar a atender. Espere o HTTP - ON aparecer no log antes de concluir que deu errado.
🔐 Provando que a chave protege mesmo
Antes de conectar qualquer WhatsApp, eu quis ter certeza de que a API não estava aberta para o mundo. Testei os três casos:
# 1. Sem chave nenhuma
curl -X POST http://SEU_IP_AQUI:8080/instance/create \
-H 'Content-Type: application/json' \
-d '{"instanceName":"invasor"}'
# 2. Com uma chave errada
curl http://SEU_IP_AQUI:8080/instance/fetchInstances \
-H "apikey: chave-errada-123"
# 3. Com a chave certa
curl http://SEU_IP_AQUI:8080/instance/fetchInstances \
-H "apikey: SUA_CHAVE_AQUI"
Os dois primeiros bateram na porta e voltaram:
{
"status": 401,
"error": "Unauthorized",
"response": {
"message": "Unauthorized"
}
}
E o terceiro passou. É um teste de trinta segundos, mas ele responde a pergunta que importa: a chave está sendo exigida de verdade, e não só decorando o arquivo de configuração.
🔥 Fechando a porta com o firewall
A VPS vem com o firewall desligado, ou seja, toda porta que um contêiner abrir fica exposta. Vamos deixar só as duas que interessam:
ufw allow 22/tcp
ufw allow 8080/tcp
ufw enable
Cuidado com a ordem: libere o 22 antes de ligar o firewall. Se você inverter, o próprio SSH cai junto e você se tranca do lado de fora da máquina. É um erro que só se comete uma vez na vida. 😬
Status: active
To Action From
-- ------ ----
[ 1] 22/tcp ALLOW IN Anywhere
[ 2] 8080/tcp ALLOW IN Anywhere
[ 3] 22/tcp (v6) ALLOW IN Anywhere (v6)
[ 4] 8080/tcp (v6) ALLOW IN Anywhere (v6)
E confira que os dois serviços continuam respondendo depois disso: SSH e API. Firewall que derruba o que devia proteger é pior que firewall nenhum.
🖱️ Entrando no painel
Agora a parte visual. Abra no navegador:
http://SEU_IP_AQUI:8080/manager
O painel pede duas coisas: a Server URL e a API Key Global. E aqui você colhe o fruto de ter arrumado a SERVER_URL lá atrás: o campo já vem preenchido sozinho com o endereço certo. Se ainda estivesse localhost, seria você preenchendo na mão e se perguntando por que não conecta.
A chave é aquela que você gerou com o openssl. Feito o login, aparece a lista de instâncias:
Repare no rodapé: Version: 2.3.7, batendo com a imagem que a gente fixou no compose. E na apikey da instância, que o painel mostra mascarada por padrão. Bom sinal, ela não fica exposta na tela para quem passar atrás de você. 👀
📱 Criando a instância e pegando o QR Code
Instância, na Evolution, é cada número de WhatsApp conectado. Dá para criar pelo botão Instance + do painel, mas eu prefiro mostrar pela API, que é o que você vai usar quando for automatizar:
curl -X POST http://SEU_IP_AQUI:8080/instance/create \
-H "apikey: SUA_CHAVE_AQUI" \
-H 'Content-Type: application/json' \
-d '{"instanceName":"tutorial","integration":"WHATSAPP-BAILEYS","qrcode":true}'
O campo integration é obrigatório na v2 e define qual motor conecta o WhatsApp. O WHATSAPP-BAILEYS é o que usa o WhatsApp Web por trás, que é o caso da maioria. A resposta traz o QR Code já embutido:
instancia: tutorial
status: connecting
hash/apikey: presente
qrcode base64: 12038 bytes
qrcode code: 2@ZcwqBoGsAcg2yKBrcnNAHx0Wm6flYX1RBaIRxc...
Dois detalhes úteis aí. O hash é uma apikey só dessa instância, e dá para entregar ela a um sistema que só pode mexer nesse número, sem passar a chave global. E o qrcode.base64 são 12 KB de imagem pronta, que você pode jogar direto num <img> se estiver montando uma tela própria.
Clicando na instância, o painel mostra o dashboard dela:
Status Connecting, contadores zerados, e o botão Get QR Code. Clicando nele:
É esse o QR que você lê no celular, em Aparelhos conectados → Conectar um aparelho. Ele expira em poucos segundos e é trocado sozinho. Se demorar para pegar o celular, não se assuste, é só clicar de novo.
E é por isso que aquela SERVER_URL importa tanto: essa tela do QR é exatamente a que fica em branco quando o painel não consegue falar com a API. O QR nem chega a ser desenhado, e não aparece erro nenhum explicando. 🫠
✅ Conectado, e o que acontece nesse instante
Assim que o celular lê o QR, o painel muda para Connected, em verde:
E olhe os contadores, porque tem uma coisa importante aí. Eles pularam de zero para 564 contatos, 673 conversas e 22 mil mensagens em poucos segundos.
Isso não é bug: é o WhatsApp Web funcionando como sempre funcionou. Quando você conecta um aparelho, ele sincroniza o seu histórico. A diferença é que, desta vez, o "aparelho" é um PostgreSQL na sua VPS, e aquelas opções DATABASE_SAVE_DATA_* que a gente ligou lá no compose estão mandando gravar tudo.
Dá para confirmar o estado pela API também, que é como o seu sistema vai perguntar "posso mandar mensagem agora?":
curl "http://SEU_IP_AQUI:8080/instance/connectionState/tutorial" \
-H "apikey: SUA_CHAVE_AQUI"
{
"instance": {
"instanceName": "tutorial",
"state": "open"
}
}
O estado open é o que interessa. Enquanto estiver connecting, o QR ainda não foi lido, e todo envio vai falhar.
💬 Enviando a primeira mensagem
Chegamos no motivo de tudo isso. O endpoint é o /message/sendText/ seguido do nome da instância:
curl -X POST "http://SEU_IP_AQUI:8080/message/sendText/tutorial" \
-H "apikey: SUA_CHAVE_AQUI" \
-H 'Content-Type: application/json' \
-d '{"number":"5511999998888","text":"Olá! Mensagem enviada pela Evolution API. 🦄"}'
O number vai com código do país e DDD, só dígitos. Nada de +, parênteses ou traço. E a resposta vem assim:
{
"key": {
"remoteJid": "[email protected]",
"fromMe": true,
"id": "3EB035D051A6DC5AAD559A"
},
"pushName": "Você",
"status": "PENDING",
"message": {
"conversation": "Olá! Mensagem enviada pela Evolution API. 🦄"
},
"messageType": "conversation",
"messageTimestamp": 1786974398,
"source": "web"
}
Dois campos merecem explicação, porque assustam à toa.
O status vem PENDING, e isso está certo. Não quer dizer que falhou: quer dizer que a Evolution aceitou a mensagem e a entregou ao WhatsApp, que ainda vai fazer o trabalho dele. É o mesmo relógio que aparece no seu celular antes do tiquinho. Se você esperar "status": "SUCCESS" na resposta do curl, vai esperar para sempre.
O id é o identificador da mensagem no WhatsApp. Guarde-o: é por ele que você vai casar os eventos de entrega e leitura, se um dia ligar webhooks.
Para grupo, muda só o formato do destino. Em vez do número, vai o ID do grupo com o sufixo @g.us:
curl -X POST "http://SEU_IP_AQUI:8080/message/sendText/tutorial" \
-H "apikey: SUA_CHAVE_AQUI" \
-H 'Content-Type: application/json' \
-d '{"number":"[email protected]","text":"Aviso para o grupo. 🚀"}'
🧯 Os dois erros que você vai ver primeiro
Vale conhecer os dois de antemão, porque as mensagens são claras, e isso é raro. 😄
Número que não existe no WhatsApp:
{
"status": 400,
"error": "Bad Request",
"response": {
"message": [
{
"jid": "[email protected]",
"exists": false,
"number": "5511999999999"
}
]
}
}
Esse "exists": false é bem útil: a Evolution checa se o número tem WhatsApp antes de tentar mandar. Ou seja, dá para usar o próprio erro como validação de lista, em vez de sair disparando para número que não existe, o que, aliás, é exatamente o tipo de comportamento que queima a reputação do seu número.
Instância com nome errado:
{
"status": 404,
"error": "Not Found",
"response": {
"message": [
"The \"naoexiste\" instance does not exist"
]
}
}
Erro bobo, mas comum: o nome da instância vai na URL, não no corpo do JSON. Trocar o nome sem trocar a URL é engano de todo dia.
🚨 O risco: como não perder o seu número
Chegamos na parte que eu mais queria escrever, e que a maioria dos tutoriais pula. 🙏
A Evolution API funciona automatizando o WhatsApp Web. Não é uma parceria com a Meta, não é um acordo, não é uma brecha autorizada: é o seu servidor se passando por um navegador conectado à sua conta. Isso está fora dos termos de uso, e o WhatsApp tem todo o direito de banir um número que ele considere abusivo.
Quando isso acontece, você não perde só o acesso à API. Você perde o número. As conversas, os grupos, o histórico, o contato que os seus clientes têm salvo há anos. E a chance de recuperar um banimento por automação é baixa.
Então a regra número um, aquela que eu repetiria em negrito no meio de qualquer reunião:
Agora, o que de fato aumenta o risco de bloqueio. Não é usar a API em si: é se comportar como robô. O WhatsApp detecta padrão, não tecnologia.
Disparo em massa para quem não pediu. É o caminho mais rápido para o banimento, sem concorrência. Mandar a mesma mensagem para 500 números comprados numa lista derruba o número em horas. A Evolution não protege você disso: ela obedece.
Número novo já disparando muito. Um chip recém-ativado que manda 300 mensagens no primeiro dia é o retrato do comportamento suspeito. Números têm uma espécie de reputação, e ela se constrói devagar: comece com poucas mensagens por dia e vá subindo ao longo de semanas, no famoso "aquecimento".
Mensagem idêntica repetida. Texto exatamente igual para muita gente é fácil de detectar. Varie: use o nome da pessoa, mude a saudação, alterne a ordem das frases. Fora que a mensagem fica melhor. 😊
Velocidade de robô. Cem mensagens em um minuto não é gente. Coloque um intervalo aleatório entre os envios, de alguns segundos, variando, e não dispare de madrugada.
Bloqueios e denúncias dos usuários. Esse é o sinal mais forte de todos. Quando muita gente bloqueia ou denuncia o seu número, o WhatsApp entende que você está incomodando, e aí não tem técnica que salve. É por isso que a saída sustentável não é disfarçar melhor: é mandar mensagem que a pessoa quer receber.
Resumindo o que dá para levar como prática: use número dedicado, mande só para quem te deu o contato, ofereça uma forma clara de sair, respeite quem pediu para parar, e cresça o volume devagar.
E vale ter na cabeça a comparação honesta, sem torcida: a API oficial custa por mensagem, mais ainda depois de outubro, mas o seu número não corre risco de sumir. A Evolution não custa por mensagem, mas o risco de banimento é seu. Para aviso interno, notificação de sistema, integração pessoal e projeto pequeno, a troca compensa com folga. Para o canal de atendimento do qual a sua empresa depende, pense duas vezes. 🤔
📊 Quanto isso tudo consome, de verdade
Voltando à promessa do começo. Com os três contêineres de pé:
docker stats --no-stream
NAME CPU % MEM USAGE / LIMIT
evolution-api 0.00% 140MiB / 7.755GiB
evolution-postgres 0.00% 39.08MiB / 7.755GiB
evolution-redis 1.86% 3.195MiB / 7.755GiB
Some: 182 MB de RAM para a stack inteira, com a CPU praticamente zerada. Numa máquina de 7,8 GB, isso é 2%.
Ou seja: aquela ideia de que "precisa de uma VPS boa para rodar Evolution" não se sustenta: a menor máquina de qualquer provedor dá conta com folga. O que come recurso não é a API parada, é o volume de mensagens depois.
O disco é outra história, e é aí que a conta pesa:
evoapicloud/evolution-api:v2.3.7 1.83GB
postgres:15-alpine 417MB
redis:7-alpine 57.8MB
A imagem da Evolution sozinha tem 1,83 GB. Não é problema de execução: é espaço em disco e tempo de download na primeira vez. Se a sua VPS tem 10 GB, dá, mas não sobra tanto quanto parece.
🔁 Os comandos do dia a dia
Guardando os que eu realmente uso, todos rodados de dentro de /opt/evolution:
docker compose ps # o que está de pé
docker compose logs -f # acompanhar os logs ao vivo (Ctrl+C sai)
docker compose restart # reiniciar sem perder nada
docker compose down # parar tudo (os dados ficam nos volumes)
docker compose up -d # subir de novo
Uma diferença que vale gravar: docker compose down remove os contêineres mas preserva os volumes, então suas instâncias e mensagens continuam lá quando você subir de novo. Já docker compose down -v, com o -v, apaga os volumes junto, e aí some tudo, inclusive as sessões de WhatsApp conectadas. Esse -v é pequenininho e faz um estrago enorme. ⚠️
Para atualizar a Evolution quando sair versão nova, troque a tag no compose e:
docker compose pull
docker compose up -d
🔒 Duas coisas antes de usar isso para valer
O que está aqui funciona, mas roda em HTTP puro, na porta 8080. Para um teste está ótimo; para uso real, duas mudanças são importantes.
A primeira é colocar HTTPS na frente, com um domínio e um proxy reverso. Sem isso, a sua apikey viaja em texto puro em toda requisição, e qualquer um no caminho da rede consegue lê-la.
A segunda é não deixar a 8080 aberta para o mundo. Se só o seu servidor de aplicação vai conversar com a Evolution, restrinja o firewall ao IP dele em vez de liberar para todos:
ufw delete allow 8080/tcp
ufw allow from IP_DO_SEU_APP to any port 8080
São dois assuntos que dão artigo próprio, e eu não vou enfiar aqui só para dizer que falei. Mas fica o aviso: uma Evolution aberta na internet com HTTP é uma porta para o seu WhatsApp, e a chave é tudo que protege ela.
No fim das contas, a conta que fecha é essa: US$ 5,50 por mês de VPS contra uma tarifa por mensagem que só cresce, pesando contra o risco de o número ir embora um dia. Sabendo desse risco e escolhendo o número certo para correr ele, a Evolution resolve muito bem a vida de quem só quer o sistema avisando as coisas no WhatsApp sem pagar pedágio por aviso. 🦄
Por hoje é só, meus unicórnios! 🦄✨
Que a magia do arco-íris continue brilhando em suas vidas! Até mais! 🌈🌟
Perguntas frequentes
A Evolution API é gratuita?
Usar a Evolution API pode banir meu número do WhatsApp?
O que aumenta o risco de bloqueio do número?
Que servidor é necessário para rodar a Evolution API?
docker stats foi de 182 MB de RAM no total e CPU praticamente zerada. O que exige atenção é o disco: só a imagem da Evolution tem 1,83 GB, e com PostgreSQL e Redis o conjunto passa de 2,3 GB.Qual a diferença entre a Evolution API e a API oficial do WhatsApp?
O comando <code>docker compose down</code> apaga as instâncias conectadas?
docker compose down remove os contêineres mas preserva os volumes, então as instâncias e as mensagens continuam lá quando você subir de novo. O que apaga tudo é o docker compose down -v: o -v remove os volumes junto, incluindo as sessões de WhatsApp já conectadas.Leia também
Webhook da Evolution API: receber e saber se foi lida
Configure o webhook da Evolution API para saber quando chega mensagem, quando ela é lida e quando a conexão cai. Com os JSONs de ida e de volta.
Evolution API: enviando todos os tipos de mensagem
Texto, imagem, áudio, vídeo, localização, contato, botões, listas e enquetes pela Evolution API: a rota de cada um, os limites e o erro que engana.