Múltiplas instâncias de WhatsApp: como estruturar para vários clientes
Automatizar o WhatsApp de um cliente é um problema de integração. Automatizar o de trinta é um problema de arquitetura — e as decisões que você toma no terceiro cliente definem se o trigésimo vai ser simples ou insuportável.
Este guia é para quem opera números de terceiros: agência que atende várias contas, SaaS que oferece WhatsApp como recurso, ou empresa com várias unidades. O foco é o que muda quando sai de um número para muitos.
O erro que custa a refatoração
O erro quase universal: começar com uma instância compartilhada entre clientes, com o sistema separando as conversas por número de destino.
Funciona com dois clientes. Depois quebra, sempre pelos mesmos motivos:
- Um cliente derruba todos. Se o número for bloqueado por excesso de disparo de um cliente, todos param.
- Não existe rateio possível. Você não sabe quanto cada cliente consome.
- O remetente é errado. O cliente do seu cliente recebe mensagem de um número que não é da empresa que ele conhece.
- Sair é impossível. Cliente que encerra o contrato quer o número dele. Se era compartilhado, não havia número dele.
A regra que evita tudo isso: uma instância por cliente, sempre. Instância é a unidade de isolamento — de risco, de custo e de identidade.
Nomenclatura: decida antes do terceiro cliente
O nome da instância vira chave em log, alerta, cobrança e suporte. Trocar depois significa mexer em tudo.
Um padrão que envelhece bem:
<cliente>-<finalidade>
padaria-silva-vendas
padaria-silva-suporte
clinica-aurora-agenda
Três regras:
Use o identificador estável do cliente, não o nome comercial. Empresa troca de nome; o registro no seu banco, não. Se o seu sistema tem cliente_id, considere c1042-vendas.
Inclua a finalidade. Cedo ou tarde um cliente vai querer separar vendas de suporte. Se o nome já prevê isso, a segunda instância não quebra o padrão.
Nada de sequencial puro. instancia-1, instancia-2 é impossível de depurar às duas da manhã.
Isolamento no seu banco
Toda tabela que toca WhatsApp precisa da coluna do cliente, e toda consulta precisa filtrar por ela. Parece óbvio até o dia em que uma consulta esquece o filtro e um cliente vê a fila do outro.
CREATE TABLE mensagens (
id BIGSERIAL PRIMARY KEY,
cliente_id BIGINT NOT NULL REFERENCES clientes(id),
instancia TEXT NOT NULL,
numero TEXT NOT NULL,
texto TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'pendente',
message_id TEXT,
criado_em TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX ON mensagens (cliente_id, status, criado_em);
Duas defesas que valem o esforço:
Derive a instância do cliente, nunca aceite as duas juntas. Se uma função recebe cliente_id e instancia como parâmetros independentes, alguém um dia vai passar a combinação errada e mandar mensagem do cliente A pelo número do cliente B. Receba só o cliente_id e busque a instância dele.
Registre o cliente_id em todo log. Sem isso, investigar "o cliente diz que não recebeu" vira busca em texto.
Webhooks: um por cliente ou um só?
As duas abordagens funcionam. A escolha depende de quem consome.
Um endpoint só, com a instância no payload. Mais simples de operar: um lugar para monitorar, um lugar para corrigir. Você identifica o cliente pela instância e roteia internamente. É o certo quando você é quem processa tudo.
Um endpoint por cliente. Necessário quando o cliente tem sistema próprio e quer receber direto. Mais superfície para monitorar, mas cada um falha sozinho.
Na prática, o arranjo mais comum é híbrido: tudo chega no seu endpoint, você grava, e depois repassa para o endpoint do cliente que tiver um. Assim você mantém o histórico mesmo quando o sistema do cliente está fora do ar.
Vale saber como o encaminhamento se comporta: a entrega é tentada três vezes, com esperas de 1, 5 e 25 segundos, e a falha definitiva fica registrada. Ou seja, uma instabilidade curta no servidor do cliente não perde o evento. Para testar o endpoint de um cliente novo antes de ligar, use o testador de webhook; o formato dos eventos está em webhook de WhatsApp.
Chaves de API: uma por cliente
Tentador usar uma chave só para tudo. Não faça.
Com uma chave por cliente você ganha três coisas:
- Revogação cirúrgica. Chave vazada revoga só aquele cliente.
- Rastreabilidade. Dá para saber qual integração fez o quê.
- Rate limit separado. O limite de requisições é por chave — com uma chave só, um cliente em pico consome a cota de todos.
Esse último ponto é o mais concreto. O limite é de 60 requisições por minuto por chave. Se você usa uma chave para trinta clientes, uma campanha de um cliente gera 429 para os outros vinte e nove. Com chaves separadas, o problema fica contido. O tratamento correto do 429 está em rate limit e fila.
Fila por cliente, não fila única
Fila única com trinta clientes tem um defeito: um cliente que enfileira 50 mil mensagens empurra os outros para o fim. O aviso de vencimento do cliente pequeno sai três horas atrasado porque o cliente grande está fazendo campanha.
Duas soluções, da mais simples para a mais robusta:
Rodízio. O worker pega um lote pequeno por cliente, em rodízio, em vez de esvaziar um antes de passar ao próximo. Resolve 90% dos casos com pouca mudança.
Filas separadas com prioridade. Mensagem transacional passa na frente de campanha, sempre — independente do cliente. Confirmação de pedido não pode esperar uma campanha terminar.
E um teto por cliente evita surpresa: se um cliente enfileirar mais do que o combinado, a fila avisa em vez de simplesmente engolir.
Quando um número cai
Com trinta instâncias, queda deixa de ser incidente e vira rotina: sempre tem alguma fora. O que muda é o processo.
Detecte por evento, não por varredura. Consultar o status de trinta instâncias em laço é lento e desperdiça requisição. Configure a URL de status de cada uma e reaja ao evento de conexão.
Pause só a fila do cliente afetado. É o principal ganho do isolamento: a queda de um não segura os outros vinte e nove.
Avise o cliente certo, na hora. Quem precisa pegar o celular e ler o QR Code é ele, não você. Um alerta automático — "o WhatsApp da sua conta desconectou, precisamos que você reconecte" — resolve mais rápido do que descobrir dias depois.
Tenha um painel com o status de todas. Uma tela com trinta linhas verdes e vermelhas responde "está tudo bem?" em um segundo.
As causas e o procedimento de reconexão estão em instância desconectou.
Custo e rateio
No modelo de mensalidade por instância, o custo é previsível e o rateio é direto: cada cliente custa uma instância. Os planos vão de 1 a 10 instâncias — Starter, Pro e Agência —, e acima disso é questão de somar contratos.
O que costuma escapar da conta:
- O tempo de suporte de reconexão. Cliente que derruba o número toda semana consome mais que a mensalidade dele.
- O número de teste. Vale ter uma instância só sua, para validar mudança antes de aplicar em cliente. É a mais barata das apólices.
- Cliente inativo que não cancelou. Instância parada custa igual. Revise a lista todo mês.
Checklist antes do décimo cliente
- Uma instância por cliente, nunca compartilhada
- Padrão de nomenclatura definido e documentado
-
cliente_idem todas as tabelas e em todos os logs - Instância derivada do cliente, nunca recebida solta como parâmetro
- Uma chave de API por cliente
- Fila com rodízio ou prioridade, não FIFO única
- Alerta de desconexão indo para o cliente certo
- Painel com status de todas as instâncias
- Instância de teste separada da produção
- Processo de saída: como devolver o número quando o contrato acaba
Resumo
Operar muitos números não é operar um número muitas vezes. O que muda é o isolamento: instância por cliente, chave por cliente, fila que não deixa um cliente atrasar o outro, e queda que afeta só quem caiu.
Quem monta assim desde o começo escala de trinta para trezentos mudando pouca coisa. Quem começa compartilhando refaz tudo no meio do caminho — geralmente com clientes em produção.