← Blog

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:

  1. Revogação cirúrgica. Chave vazada revoga só aquele cliente.
  2. Rastreabilidade. Dá para saber qual integração fez o quê.
  3. 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_id em 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.