Warmup de WhatsApp:
a curva phased que evita ban (com código)
Pular warmup é o caminho mais rápido pro banimento. Conta nova que dispara 500 mensagens no primeiro dia tem ~80% de chance de cair em 72 horas. Conta nova que segue a curva phased correta, em 30 dias, ban-rate fica em menos de 1%. Esse artigo cobre os números exatos, a lógica por trás, e código pronto pra implementar.
Nota de transparência: esse é um padrão educacional do que a comunidade de operadores não-oficiais aprendeu rodando WhatsApp em escala. A Catcher hoje implementa throttle por instância com auto-pause em sinais de rate-limit + termômetro de contato (limita por reciprocidade do destinatário) — uma escolha estrutural diferente da curva phased tradicional, com o mesmo objetivo de não destoar da Meta. Os dois caminhos funcionam; o post abaixo é o canônico do mercado.
Antes de entrar nos números, vale entender por quê warmup funciona. Não é folclore — é mecanismo de detecção da Meta funcionando como esperado.
Por que warmup é necessário
Quando alguém compra um número novo de celular e instala o WhatsApp pela primeira vez, o padrão natural de uso é: dia 1, talvez 5-10 mensagens (testando, mandando "oi mãe"); semana 1, talvez 50-100 mensagens; mês 1, talvez 200-500. É a curva natural de adoção humana. A pessoa não chega no dia 1 mandando 1.000 mensagens pra desconhecidos.
A Meta aprendeu esse padrão observando bilhões de contas reais. Quando uma nova conta destoa radicalmente da curva — dia 1 com 500 mensagens, broadcast pra 200 contatos não-salvos, todas mensagens iguais — o sistema de detecção marca como "comportamento incompatível com humano novo" e dispara revisão. Revisão geralmente termina em ban.
Warmup é literalmente imitar a curva natural. Você sobe o volume gradualmente nas primeiras semanas, dando ao número tempo de "envelhecer" do ponto de vista da Meta. Quando atinge 30 dias com a curva respeitada, o número entra na categoria "conta estabelecida" e pode operar volume mais alto sem disparar flags.
A curva exata que rodamos em produção
A curva abaixo é o consenso de mercado entre operadores não-oficiais. Catcher roda 312 instâncias ativas com ban-rate observado em 0,3% — bem abaixo da média de ~12% — usando throttle por instância + termômetro de contato em vez de curva phased tradicional, mas o objetivo é o mesmo (não destoar do que a Meta espera de uma conta nova). Aqui estão os números fase por fase do padrão clássico:
| Fase | Período | Limite diário | Distribuição |
|---|---|---|---|
| Phase 0 | Dias 0-2 | 0 outbound (só receive) | Conta só recebe |
| Phase 1 | Dias 3-7 | 20 mensagens/dia | 2-4/hora, espaçadas |
| Phase 2 | Dias 8-14 | 50 mensagens/dia | 3-7/hora |
| Phase 3 | Dias 15-21 | 100 mensagens/dia | 5-15/hora |
| Phase 4 | Dias 22-28 | 150 mensagens/dia | 10-20/hora |
| Phase 5 | Dias 29-35 | 200 mensagens/dia | 15-30/hora |
| Phase 6 | Dias 36-42 | 300 mensagens/dia | 25-45/hora |
| Phase 7 | Dia 43+ | 500 mensagens/dia | 30-70/hora |
Notas importantes sobre a tabela:
- Phase 0 (receive-only) é frequentemente pulada e é o erro #1. Nos primeiros 2 dias, deixe a conta só receber mensagens. Se possível, peça pra alguém de confiança mandar mensagem pra você (e responda manualmente como humano). Esse "primeiro contato" gera histórico social no perfil — Meta vê que você respondeu humano, não saiu spam-ando.
- Limites são por dia natural, não janela de 24h móvel. Se você bateu 100 mensagens hoje, espere amanhã pra mandar mais — mesmo que tecnicamente tenham passado 12h.
- Distribuição importa tanto quanto volume. Mandar 100 mensagens em 3 horas é pior que mandar 100 espalhadas em 8-10 horas. Respeite ciclo circadiano (poucas mensagens entre 23h-7h).
- 500/dia é o teto sustentável. Volumes acima exigem múltiplos números (vide post sobre multi-número em produção) ou aceitar ban-rate maior.
Por que essas fases específicas
A curva não é arbitrária. Cada bracket reflete observação empírica sobre quando a Meta começa a "tolerar" volumes maiores:
Dias 0-2 receive-only: simulação do humano novo configurando o número. Recebe verificação SMS, recebe mensagens de boas-vindas, talvez responde 1-2. Não inicia conversa com desconhecido. Esse padrão é estatisticamente esperado pra conta nova.
Dias 3-7 com 20/dia: humano começou a usar pra valer. Mandando pra contatos próximos (família, amigos), ainda volume baixo. Meta tolera porque é coerente com onboarding.
Dias 8-14 com 50/dia: conta amadureceu uma semana. Volume cresceu por uso natural. Meta vê histórico de mensagens consistente com humano ativo.
Dias 15-21 com 100/dia: conta com 2 semanas é categorizada como "estabelecida". Meta começa a relaxar threshold. Você ganha margem.
Dias 22+ com escalada gradual: a partir daqui, cada bracket adicional reflete confiança crescente da Meta. Pular fase aqui (sair de 100 direto pra 500) ainda gera flag — a curva precisa parecer orgânica.
Pular qualquer fase no início (especialmente Phase 0 e Phase 1) é o que mata a maioria das contas. As pessoas leem "30 dias de warmup" e tentam comprimir em 7. Não funciona.
Catcher Pro
Throttle por instância + termômetro de contato em todas as instâncias.
Você não precisa implementar curva phased manualmente — a Catcher aplica throttle stateful por instância (auto-pause em rate-limit, hard-pause após 4 throttles) + termômetro de contato (score 0-100 que limita envios sem reciprocidade). Mesmo objetivo, abordagem estrutural diferente. R$ 97/mês.
Começar trial 7 dias →Implementação — código pra você adaptar
Se você está construindo sua própria infraestrutura, aqui vai o esqueleto da lógica. Linguagem é Go (que é o que usamos no Catcher), mas a estrutura é portável pra qualquer linguagem.
1. Definição das fases
// PhaseLimits define a curva phased de warmup.
// Cada fase tem [dias_inicio, limite_diario, hourly_min, hourly_max].
type Phase struct {
DayStart int // dias desde o pareamento
DailyLimit int // máximo de mensagens outbound nesse bracket
HourlyMin int // distribuição mínima por hora
HourlyMax int // distribuição máxima por hora
}
var WarmupPhases = []Phase{
{DayStart: 0, DailyLimit: 0, HourlyMin: 0, HourlyMax: 0}, // receive-only
{DayStart: 3, DailyLimit: 20, HourlyMin: 2, HourlyMax: 4},
{DayStart: 8, DailyLimit: 50, HourlyMin: 3, HourlyMax: 7},
{DayStart: 15, DailyLimit: 100, HourlyMin: 5, HourlyMax: 15},
{DayStart: 22, DailyLimit: 150, HourlyMin: 10, HourlyMax: 20},
{DayStart: 29, DailyLimit: 200, HourlyMin: 15, HourlyMax: 30},
{DayStart: 36, DailyLimit: 300, HourlyMin: 25, HourlyMax: 45},
{DayStart: 43, DailyLimit: 500, HourlyMin: 30, HourlyMax: 70},
}
2. Determinar a fase atual
// PhaseForAge retorna a fase aplicável dado a idade da instância.
func PhaseForAge(daysSincePair int) Phase {
// Itera de trás pra frente — pega a maior fase aplicável.
for i := len(WarmupPhases) - 1; i >= 0; i-- {
if daysSincePair >= WarmupPhases[i].DayStart {
return WarmupPhases[i]
}
}
return WarmupPhases[0] // fallback fase 0
}
// Uso:
age := int(time.Since(instance.PairedAt).Hours() / 24)
phase := PhaseForAge(age)
// agora phase.DailyLimit te diz o máximo de mensagens hoje
3. Reservar slot antes de enviar
// TryReserveWarmupSend tenta reservar 1 mensagem no bracket atual.
// Retorna ok=true se há slot, false se atingiu limite diário ou hourly window.
func TryReserveWarmupSend(instanceID string, redis *RedisClient) (bool, error) {
age := getInstanceAge(instanceID)
phase := PhaseForAge(age)
if phase.DailyLimit == 0 {
return false, nil // phase 0 — receive-only
}
// Daily counter (reseta às 00h)
dailyKey := fmt.Sprintf("warmup:%s:daily:%s",
instanceID,
time.Now().Format("2006-01-02"))
dailyCount, _ := redis.Incr(dailyKey)
if dailyCount == 1 {
redis.Expire(dailyKey, 25*time.Hour) // TTL pra limpeza
}
if int(dailyCount) > phase.DailyLimit {
return false, nil // bateu limite diário
}
// Hourly window check (5min sliding window)
hourKey := fmt.Sprintf("warmup:%s:hour:%d",
instanceID,
time.Now().Unix()/300) // bucket de 5min
hourCount, _ := redis.Incr(hourKey)
if hourCount == 1 {
redis.Expire(hourKey, 6*time.Minute)
}
// Calcula limite por bucket de 5min baseado em hourly max
bucketLimit := (phase.HourlyMax * 5) / 60
if bucketLimit < 1 {
bucketLimit = 1
}
if int(hourCount) > bucketLimit {
return false, nil // muito rápido nessa janela
}
return true, nil
}
4. Wire na pipeline de envio
// Antes de cada envio, checar warmup
func (h *MessageHandler) SendText(req SendTextRequest) error {
ok, err := TryReserveWarmupSend(req.InstanceID, h.redis)
if err != nil {
return err
}
if !ok {
// Não pode enviar agora — re-enfileira pra mais tarde
return h.queue.Reschedule(req, 15*time.Minute)
}
// Adiciona jitter natural antes de enviar (2-8s)
jitter := time.Duration(2+rand.Intn(6)) * time.Second
time.Sleep(jitter)
return h.whatsapp.Send(req)
}
Erros comuns que matam o warmup
Erro 1 — Pular Phase 0 (receive-only)
Tentação universal: você acabou de parear o número, quer testar mandando uma mensagem pra você mesmo. Não faça. A Phase 0 existe pra simular o humano que recebeu o número e ainda não começou a usar pra valer. Mande mensagem do número novo pro número novo é tecnicamente "outbound" e conta como início de atividade.
Erro 2 — Acumular volume não-usado em dias seguintes
Erro comum: "ah, ontem eu mandei só 5 das 20 que podia. Hoje mando 35 pra compensar." Não funciona — o limite é por dia natural, não acumulável. Os 15 não-usados de ontem não viraram crédito hoje. Se você passar dos 20 de hoje, você está acima do bracket.
Erro 3 — Concentrar tudo em 2 horas
Mandar todas as 100 mensagens permitidas hoje entre 14h e 16h é pior que mandar 50 espalhadas o dia inteiro. Concentração temporal alta dispara flag de "burst" mesmo dentro do limite diário. Use ciclo circadiano: 70% do volume entre 9h-19h, 25% entre 7h-9h e 19h-22h, 5% no resto. Praticamente nada entre 23h-7h.
Erro 4 — Ignorar respostas
Se você manda 50 mensagens no dia e ninguém responde uma, é flag estatística (em humano natural, ~10-30% das conversas geram resposta no mesmo dia). Você pode (e deve) simular recepção: tenha alguém de confiança mandar mensagem pro número novo periodicamente nos primeiros 30 dias. Conta com fluxo bidirecional saudável passa quase invisível por detecção.
Erro 5 — Trocar conteúdo idêntico em escala
Mesmo dentro do limite phased, se as 20 mensagens do dia 5 são todas idênticas ("Olá! Confira nossa promoção: [link]"), a Meta detecta o template e marca. Use pelo menos 5 variantes estruturalmente diferentes na mesma campanha. Variar destinatário não basta — variar a mensagem é essencial.
Erro 6 — Ignorar feedback de bloqueio
Se um destinatário bloqueia o seu número logo nos primeiros 7 dias, isso conta como sinal forte de spam. Múltiplos bloqueios em janela curta acelera a curva pra ban. Se sua plataforma emite webhook de bloqueio (Catcher emite), monitore — mais de 3 bloqueios em 24h é hora de pausar e investigar lista.
Quando warmup termina
Tecnicamente, "termina" no dia 43 quando você atinge o limite máximo sustentável (500/dia). Mas cuidado: passar de 500/dia exige decisão estratégica. Você pode:
- Adicionar mais um número e fazer warmup nele. Em 30-43 dias, você tem 1.000/dia em 2 números. Em 90 dias, 5.000/dia em 10 números. É a estratégia que escala saudável.
- Aceitar ban-rate maior empurrando 1 número pra 800-1000/dia. Cada número que pula pra 800/dia tem ~15-25% chance de cair em 60 dias. Se a economia justifica, OK — mas tenha plano de reposição.
- Migrar pra API oficial Meta se compliance exige (vide comparativo oficial vs não-oficial).
A maioria dos operadores escolhe (1) — escalar via número adicional. Por isso multi-número é praticamente obrigatório em qualquer operação que vai além de 500 envios/dia.
Como saber se sua conta passou pelo warmup com sucesso
Sinais positivos no dia 43:
- 0 banimentos no histórico.
- Taxa de entrega > 95% (mensagens chegando ao destinatário).
- Sem warning visível no app (alguns países mostram "this account is at risk" pro próprio operador).
- Sem suspensão temporária (24h ou 48h freezes que precedem ban definitivo).
- Recebimento de mensagens fluindo normal (a Meta às vezes silenciosamente reduz delivery sem banir).
Sinais de alerta no warmup:
- Mensagem ficou marcada como "1 check" (servidor recebeu) por mais de algumas horas — possível shadow-ban parcial.
- Taxa de bloqueio acima de 1% — sua lista pode não ser opt-in.
- Suspensão temporária 24h — você bateu em algum threshold. Volte uma fase, espere passar.
- Mensagens não chegando em destinatários conhecidos seus — provável shadow-ban.
Em qualquer alerta, pause envios. Reduza o volume pra 50% do bracket atual. Espere 7 dias antes de tentar voltar à curva.
O que a Catcher faz por padrão
Em cada instância nova:
- Phase 0 ativada automaticamente nos primeiros 2 dias (qualquer envio retorna erro
WARMUP_PHASE_RECEIVE_ONLY). - Curva de Phase 1 a Phase 7 aplicada automaticamente conforme idade da instância.
- Throttle por instância em Redis com checagem em todo envio.
- Distribuição com jitter natural (2-8s entre mensagens) e respeito a ciclo circadiano.
- Webhook quando instância está pra cruzar bracket (você sabe quando o limite vai subir).
- Pause automático em sinais de risco (bloqueios em sequência, suspensão temporária da Meta, padrão estranho).
Tudo configurável via API ou Console. Você pode forçar bypass do warmup (com seu próprio risk) se for caso de migração de número que já estava em produção em outro lugar — vide endpoint POST /v1/instances/{id}/warmup/bypass na nossa documentação.
Resumo executivo
- Warmup é obrigatório, não opcional. Pular = ~80% chance de ban em 72h.
- Curva de 8 fases ao longo de 43 dias, atingindo o teto de 500/dia.
- Phase 0 (receive-only) é o passo mais pulado e o erro mais caro.
- Distribuição matters tanto quanto volume. Espalhe ao longo do dia, respeite circadiano.
- 500/dia é o teto sustentável. Acima disso, escale via mais números.
- Catcher faz isso automaticamente, mas se você está construindo do zero, copie a curva acima.
Não quer implementar a curva manualmente?
Catcher Pro · Warmup automático em cada instância
A partir de R$ 47/instância em volume. Curva phased de 8 fases ativada por padrão. Throttle por Redis, jitter natural, pause em sinais de risco.