Raphael Serafim· Publicado em 10 de setembro de 2026· 10 min de leitura

Provisionar WhatsApp para 100 clientes por API: criar, ativar, suspender e cortar

Se criar a conta de WhatsApp de um cliente novo depende de alguém abrir um painel, o processo trava no décimo. Como amarrar o ciclo de vida da instância ao seu billing: criar no onboarding, suspender na inadimplência e cortar no cancelamento.

Ver como Markdown

A pergunta que separa uma integração de um produto: o que acontece quando entra o cliente número 11?

Se a resposta envolve alguém abrindo um painel, preenchendo um formulário e copiando uma chave para o seu sistema, o processo trava — e o custo por cliente cresce com o número de clientes, que é exatamente o contrário do que uma software house precisa.

Provisionamento no onboarding

O modelo que escala amarra a instância ao contrato:

async function ativarCliente(clienteId) {
  const cliente = await db.clientes.buscar(clienteId);

  // 1. cria a instância
  const inst = await wameAdmin.criarInstancia({
    nome: `cliente-${cliente.id}`,
    // Nome legível ajuda no dia em que você precisar auditar
    // a fatura consolidada e descobrir de quem é cada linha.
  });

  // 2. guarda a chave junto do cliente
  await db.clientes.atualizar(clienteId, {
    wame_instancia_id: inst.id,
    wame_key: cifrar(inst.key),
    canal_status: 'aguardando_conexao',
  });

  // 3. aponta o webhook para a URL DESTE cliente
  await wameAdmin.configurarWebhook(inst.key, {
    allowWebhook: true,
    webhookFormat: 'meta',
    webhookMessage: `https://seusistema.com/webhook/wame/${cliente.slug}/${cliente.webhookSecret}`,
  });

  return inst;
}

O cliente termina o cadastro no seu sistema e a instância já existe. Ninguém abre painel de ninguém.

Uma URL de webhook por cliente, com segredo próprio. Não use uma URL única para todos: além de você precisar descobrir de quem é cada evento, um segredo vazado comprometeria a base inteira. É o mesmo raciocínio de webhook em produção.

Conectar o número do cliente

Duas portas, e a escolha é por cliente:

API oficial — login seguro pela própria Meta, dentro do seu fluxo. O número fica no Business Manager do cliente, e ele nunca digita credencial no seu sistema.

API não oficial — QR Code. Você busca o código e mostra na sua tela:

const { qr } = await wa.instance.connect();
// devolva o QR para o front do SEU produto renderizar

O cliente escaneia dentro do seu sistema, com a sua marca. Nenhuma tela de terceiro aparece.

Cliente pequeno começa no QR Code hoje; cliente que precisa de contrato vai para o oficial. O comparativo entre os dois ajuda a decidir, e mudar depois não reescreve o seu código — muda a instância, o handler continua o mesmo.

Estados do contrato viram estados da instância

O erro clássico é tratar os dois como coisas separadas. O certo é o contrato mandar:

ContratoInstânciaChamada
AtivoAtiva
InadimplenteSuspensadesativar
PagouAtivaativar
CanceladoExcluída (após carência)excluir
Trial vencidoSuspensadesativar
async function aplicarEstado(clienteId, novoEstado) {
  const c = await db.clientes.buscar(clienteId);
  if (!c.wame_key) return;

  switch (novoEstado) {
    case 'inadimplente':
    case 'trial_vencido':
      // Suspender, não excluir: reativação depois do pagamento
      // precisa ser imediata, e excluir perderia a conexão.
      await wameAdmin.desativar(c.wame_key);
      break;

    case 'ativo':
      await wameAdmin.ativar(c.wame_key);
      break;

    case 'cancelado':
      // Carência antes de excluir. Cancelamento por engano
      // acontece, e excluir é irreversível.
      await agendar('excluir_instancia', { clienteId }, { emDias: 30 });
      break;
  }

  await db.clientes.atualizar(clienteId, { canal_status: novoEstado });
}

Suspender em vez de excluir é a decisão que mais evita dor de cabeça. Inadimplência costuma ser temporária; exclusão não é.

Carência antes de excluir cobre o cancelamento por engano — e ele acontece mais do que se imagina.

A reconciliação que paga a própria conta

Estado divergente é inevitável: uma chamada falha, um webhook se perde, alguém muda o contrato direto no banco. O resultado é sempre o mesmo — instância ativa de cliente que já saiu, aparecendo na sua fatura.

// roda todo dia de madrugada
async function reconciliar() {
  const clientes = await db.clientes.comInstancia();
  const instancias = await wameAdmin.listarInstancias();
  const porChave = new Map(instancias.map((i) => [i.key, i]));

  const divergencias = [];

  for (const c of clientes) {
    const inst = porChave.get(decifrar(c.wame_key));

    if (!inst) {
      divergencias.push({ cliente: c.id, problema: 'instancia_sumiu' });
      continue;
    }

    const deveriaEstarAtiva = c.canal_status === 'ativo';
    if (inst.ativa !== deveriaEstarAtiva) {
      divergencias.push({
        cliente: c.id,
        problema: 'estado_divergente',
        contrato: c.canal_status,
        instancia: inst.ativa ? 'ativa' : 'inativa',
      });
      await aplicarEstado(c.id, c.canal_status);   // corrige
    }
  }

  // Órfãs: existem na fatura e não pertencem a ninguém
  const chavesConhecidas = new Set(clientes.map((c) => decifrar(c.wame_key)));
  for (const i of instancias) {
    if (!chavesConhecidas.has(i.key)) {
      divergencias.push({ problema: 'instancia_orfa', key: i.key });
    }
  }

  if (divergencias.length) await avisarTime(divergencias);
}

Instância órfã é dinheiro indo embora todo mês em silêncio. Meia hora de código que se paga na primeira fatura.

Monitorar a saúde de todas

Com 100 clientes, você não descobre que a instância caiu pelo cliente ligando:

async function verificarSaude() {
  const ativos = await db.clientes.ativos();

  for (const c of ativos) {
    const info = await wa(c).instance.info();

    if (!info.conectada) {
      await registrarIncidente(c.id, 'desconectada');
      // Avise o cliente ANTES de ele perceber. Muda completamente
      // a conversa: de "seu sistema está quebrado" para "vimos que
      // caiu e já estamos resolvendo".
      await notificarCliente(c, 'canal_desconectado');
    }
  }
}

Na API não oficial isso importa mais: a sessão pode cair sozinha e precisa de reconexão.

Segurança do multi-tenant

Cifre a chave da instância no banco. Ela permite enviar mensagem em nome do número do cliente. Vazamento de banco não pode virar vazamento de canal.

Nunca exponha a chave no front. Toda chamada sai do seu servidor. Chave no bundle é chave pública.

Isole por cliente em toda consulta. O WHERE cliente_id = ? esquecido é o bug que manda a mensagem de um cliente para a base de outro.

Segredo de webhook por instância, como no exemplo lá em cima.

A fatura

No Programa de Parceiros, todas as instâncias vêm numa fatura mensal única, com preço por volume — R$ 28,00 por instância na faixa de 20 a 99, caindo conforme cresce. Você paga uma vez e cobra dos seus clientes do seu jeito, no seu ciclo.

Como transformar isso em margem previsível está em quanto cobrar pelo módulo.

Conclusão

Provisionamento por API é o que faz o décimo cliente custar o mesmo que o segundo. Sem ele, cada venda nova adiciona trabalho manual, e a operação encontra um teto que não é comercial — é de processo.

Quatro peças resolvem: criar no onboarding, mapear estado do contrato para estado da instância, reconciliar todo dia e monitorar a saúde. Nenhuma é difícil; a que mais se esquece é a reconciliação, e é justamente a que aparece na fatura.

O desenho completo, incluindo a parte comercial, está na página para software house.

Pronto para automatizar seu WhatsApp?

Crie sua conta gratuita e comece a enviar mensagens pela API em minutos.

Começar grátis

Perguntas frequentes

Dá para criar instâncias de WhatsApp pela API, sem painel?+

Sim. Criar, ativar, desativar e excluir são chamadas de API. Isso permite que o provisionamento aconteça dentro do seu próprio onboarding: o cliente termina o cadastro no seu sistema e a instância já existe, sem ninguém abrir painel de terceiro.

Como amarrar a instância ao ciclo de vida do cliente?+

Tratando o estado do contrato como fonte da verdade e a instância como consequência. Contrato ativo, instância ativa; contrato inadimplente, instância suspensa; contrato cancelado, instância excluída após um período de carência. Um job diário reconcilia os dois estados e corrige divergência.

O cliente final precisa saber que existe um fornecedor por trás?+

Não. Todo o gerenciamento acontece via API dentro do seu produto, e a cobrança é a sua. O cliente vê o WhatsApp funcionando no sistema que você entregou e conecta o número dele por um login da própria Meta ou por QR Code.

O que acontece com a instância se o cliente não pagar?+

Isso é decisão sua, e é por isso que o controle por API importa. O padrão que funciona é suspender em vez de excluir: a instância para de enviar, mas o histórico e a conexão continuam, então a reativação após o pagamento é imediata.

Como evitar pagar por instância de cliente que já saiu?+

Com reconciliação automática. A causa mais comum de desperdício é o cancelamento registrado no contrato e a instância esquecida ativa. Um job diário que compara os dois lados e reporta divergência resolve — e paga o próprio desenvolvimento no primeiro mês.

Continue lendo