Raphael Serafim· Publicado em 28 de setembro de 2026· 9 min de leitura

Onboarding de WhatsApp no seu SaaS com código de pareamento (sem QR, com a sua marca)

Conecte o WhatsApp do cliente dentro do seu SaaS, com a sua marca e sem QR Code: fluxo, estados da tela, backend em Node.js e webhook de conexão.

Ver como Markdown

Para conectar o WhatsApp do cliente dentro do seu SaaS sem QR Code, seu backend chama POST /{key}/instance/pairing-code com o número do cliente, recebe o código de 8 caracteres em code e mostra na sua tela; o cliente digita no próprio celular e o webhook de conexão avisa quando terminou. Tudo acontece com a sua marca, na sua interface, e funciona até quando o cliente está com o celular como único aparelho.

Este guia é para quem desenvolve o produto: estados da tela, backend em Node.js, confirmação por webhook e as métricas do funil. A visão geral dos métodos de conexão está em conectar WhatsApp na API sem QR Code, e o passo a passo para o usuário final está em conectar o WhatsApp só pelo celular.

A camada não oficial da WAME é independente e não é afiliada, endossada ou suportada pelo WhatsApp ou pela Meta. O uso é de responsabilidade de quem envia.

Por que código em vez de QR no onboarding

O QR Code tem um problema estrutural para SaaS: ele exige duas telas. O cliente precisa ver o QR no computador e ler com o celular. Quem faz o cadastro pelo celular (e no pequeno negócio isso é muito comum) trava ali: não tem como o celular ler um QR exibido nele mesmo.

O código de pareamento resolve isso e ainda traz outras vantagens:

  • Funciona no mesmo aparelho. O cliente copia o código e cola no WhatsApp.
  • Não depende de câmera, de enquadramento ou de o QR não expirar enquanto a pessoa procura o celular.
  • Funciona remoto. O suporte consegue guiar por telefone: "abra Aparelhos conectados e digite o código que aparece na tela".
  • É white label. O cliente vê a sua tela e as suas instruções.

Em produto, isso vira métrica: menos gente abandonando o onboarding e menos chamados do tipo "não consigo ler o QR".

Os estados da tela

Antes do código, desenhe a máquina de estados. É ela que evita tela travada e cliente perdido.

EstadoO que a tela mostraPróximo
pedir_numeroCampo de telefone com DDIgerando
gerandoCarregandomostrando_codigo, conectado ou erro
mostrando_codigoCódigo grande, botão copiar, instruçõesconectado ou expirado
expirado"O código expirou" e botão para gerar outrogerando
conectadoConfirmação e próximo passo do onboardingfim
erroMensagem clara e opção de tentar de novopedir_numero

Três detalhes de UX que fazem diferença:

  1. Mostre o código grande, em blocos (XXXX-XXXX), com botão Copiar. É assim que o painel da própria WAME faz.
  2. Instruções ao lado do código, e não em outra página: "WhatsApp → Aparelhos conectados → Conectar um aparelho → Conectar com número de telefone".
  3. Ofereça o QR como alternativa, com um link discreto ("Prefere ler um QR Code?"), para quem está no computador.

Normalizando o número

O phoneNumber precisa estar no formato internacional, só dígitos, e ser o número do celular onde o código será digitado. Normalize no backend, nunca confie no que veio do formulário:

javascript
function normalizarTelefone(entrada, ddiPadrao = '55') {
  let d = String(entrada).replace(/\D/g, '');
  if (d.startsWith('00')) d = d.slice(2);
  // Número brasileiro sem DDI: 10 ou 11 dígitos (DDD + número)
  if (d.length === 10 || d.length === 11) d = ddiPadrao + d;
  if (d.length < 12 || d.length > 15) return null;
  return d;
}

No front, deixe claro que é o número do WhatsApp que será conectado, e não um telefone de contato qualquer. É a causa mais comum de "o código não funciona".

O backend: gerar o código

A key da instância autentica todas as chamadas. Ela nunca vai para o navegador. O front chama o seu backend, que sabe qual instância pertence ao cliente logado:

javascript
import express from 'express';

const app = express();
app.use(express.json());
const BASE = 'https://us.api-wa.me';

// Cliente autenticado pede o código para o próprio número
app.post('/api/whatsapp/codigo', exigirLogin, async (req, res) => {
  const cliente = await db.clientes.buscar(req.user.id);
  const telefone = normalizarTelefone(req.body.telefone);
  if (!telefone) return res.status(400).json({ erro: 'telefone_invalido' });

  const r = await fetch(`${BASE}/${cliente.instanceKey}/instance/pairing-code`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ phoneNumber: telefone }),
  });
  const dados = await r.json();

  if (!r.ok) {
    await metricas.registrar('onboarding_erro', { cliente: cliente.id, status: r.status });
    return res.status(502).json({ erro: 'falha_ao_gerar' });
  }

  // Instância já pareada: não vem código, e está tudo certo
  if (dados.isConnected) {
    await db.clientes.marcarConectado(cliente.id);
    return res.json({ estado: 'conectado' });
  }

  await db.clientes.atualizar(cliente.id, {
    whatsappStatus: 'aguardando_codigo',
    codigoGeradoEm: new Date(),
  });
  await metricas.registrar('onboarding_codigo_gerado', { cliente: cliente.id });

  res.json({ estado: 'mostrando_codigo', codigo: dados.code });
});

Repare no isConnected. A API não gera código para uma sessão que já está pareada, porque pedir código numa sessão ativa derrubaria a conexão existente. Para o seu produto, isso é sucesso: pule direto para o próximo passo.

Se a sua plataforma cria instâncias por cliente, o instanceKey vem do provisionamento. O ciclo de criar, ativar, suspender e remover está em provisionar WhatsApp para vários clientes por API.

Um caso à parte: instâncias criadas para registro mobile, sem aparelho vinculado, não usam código de pareamento e respondem com erro 400. Esse fluxo é outro, descrito em WhatsApp sem celular.

Confirmando a conexão pelo webhook

Configure o webhook de conexão da instância uma vez, no provisionamento:

bash
curl -X PUT "https://us.api-wa.me/SUA_KEY/instance" \
  -H "Content-Type: application/json" \
  -d '{
    "allowWebhook": true,
    "allowNumber": "all",
    "webhookConnection": "https://seusaas.com.br/wame/conexao",
    "webhookFormat": "meta"
  }'

Quando o cliente digita o código e o pareamento conclui, chega um evento com field: "connection" e value.connection.status igual a "open". O campo instance, no topo do envelope, traz a key da instância que conectou:

javascript
app.post('/wame/conexao', async (req, res) => {
  res.sendStatus(200); // responda rápido; processe depois

  const entry = req.body?.entry?.[0];
  const change = entry?.changes?.[0];
  if (change?.field !== 'connection') return;

  const cliente = await db.clientes.porInstancia(req.body.instance);
  if (!cliente) return;

  if (change.value?.connection?.status === 'open') {
    await db.clientes.marcarConectado(cliente.id);
    await metricas.registrar('onboarding_conectado', { cliente: cliente.id });
    avisarFront(cliente.id, { estado: 'conectado' });
  }
});

Proteja esse endpoint como qualquer webhook: URL difícil de adivinhar, validação de origem e idempotência. Os cuidados estão em segurança da API do WhatsApp: token, webhook e dados.

Levando o status até a tela

O webhook chega no backend; a tela do cliente precisa saber. Duas opções simples:

Server-Sent Events, que empurra o status assim que ele muda:

javascript
const conexoes = new Map(); // clienteId -> res

app.get('/api/whatsapp/status', exigirLogin, (req, res) => {
  res.set({ 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache' });
  res.flushHeaders();
  conexoes.set(req.user.id, res);
  req.on('close', () => conexoes.delete(req.user.id));
});

function avisarFront(clienteId, evento) {
  conexoes.get(clienteId)?.write(`data: ${JSON.stringify(evento)}\n\n`);
}

No front, poucas linhas:

javascript
const fonte = new EventSource('/api/whatsapp/status');
fonte.onmessage = (e) => {
  const { estado } = JSON.parse(e.data);
  if (estado === 'conectado') mostrarTela('conectado');
};

Polling, se a sua infraestrutura não mantém conexões abertas: o front consulta o seu backend a cada poucos segundos, e o backend responde com o status do banco.

Nos dois casos, tenha uma rede de segurança. Se o webhook falhar ou atrasar, o backend consulta GET /{key}/instance para ver o estado real antes de declarar o código como expirado.

Expiração e "gerar outro"

O código vale por pouco tempo, na casa de poucos minutos, e é de uso único. Não tente adivinhar o momento exato da expiração. Use um tempo de tela conservador:

javascript
const TEMPO_TELA_MS = 2 * 60 * 1000; // ajuste ao que você observar

setTimeout(async () => {
  const { estado } = await fetch('/api/whatsapp/estado').then((r) => r.json());
  if (estado !== 'conectado') mostrarTela('expirado');
}, TEMPO_TELA_MS);

Na tela expirado, um botão Gerar novo código volta para gerando. Limite quantas vezes por minuto o mesmo cliente pode pedir código: além de evitar abuso do seu endpoint, isso impede que um script com erro fique gerando códigos em loop.

Segurança: o código é uma senha

O código de pareamento vincula um aparelho à conta do cliente. Trate como senha:

  • Mostre só em tela autenticada, para o dono da conta.
  • Não envie o código por e-mail, SMS ou chat de suporte. O cliente gera e digita do lado dele.
  • Não registre o código em log nem em ferramenta de analytics.
  • Eduque o cliente: "nunca informe esse código a ninguém, nem ao nosso suporte". Isso também protege contra o golpe clássico em que alguém pede "o código que chegou no seu WhatsApp".

Medindo o funil

Com os eventos acima, você tem o funil de onboarding do WhatsApp:

EtapaEvento
IniciouAbriu a tela de conexão
Gerou códigoonboarding_codigo_gerado
Conectouonboarding_conectado
Expirou sem conectarTela expirado exibida
Erroonboarding_erro

Acompanhe o tempo entre gerar e conectar, a taxa de expiração e quantos precisaram de um segundo código. Taxa de expiração alta quase sempre é instrução pouco clara ou número errado. Melhore o texto da tela antes de mexer em qualquer outra coisa.

Depois do onboarding

Conectado, o cliente começa a usar o seu produto. Se o número for novo, oriente sobre ritmo nas primeiras semanas: número recém-conectado que dispara volume alto chama atenção do anti-spam. A WAME não apoia spam, e a taxa de bloqueio é muito baixa para quem usa do jeito certo: atendimento, notificações para quem pediu, grupos e bots. O plano está em como aquecer um número novo.

Também vale ligar o webhook de conexão ao seu monitoramento, e não só ao onboarding. Quedas depois de conectado são sinal a acompanhar, como mostra sinais de que o número está em risco.

Conclusão

Onboarding de WhatsApp com código de pareamento é o fluxo que o seu cliente consegue concluir sozinho, pelo celular, sem QR e sem chamar o suporte. A receita: normalize o número no backend, chame POST /{key}/instance/pairing-code com a key do cliente (nunca no navegador), mostre o code grande com botão de copiar, trate isConnected como sucesso e confirme pelo webhook de conexão com status: "open". Adicione a tela de código expirado, meça o funil e trate o código como senha. O resultado é um onboarding white label, com a sua marca, e menos gente parada no primeiro passo. A referência completa dos endpoints está na documentação.

Pronto para automatizar seu WhatsApp?

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

Começar grátis

Perguntas frequentes

Como conectar o WhatsApp do meu cliente sem mandar QR Code?+

Na tela de onboarding do seu sistema, o cliente informa o número. Seu backend chama POST /{key}/instance/pairing-code com o phoneNumber, recebe o campo code e mostra os 8 caracteres na tela. O cliente digita o código no próprio celular, em Aparelhos conectados > Conectar com número de telefone. O webhook de conexão avisa quando terminou.

Posso chamar a API de pareamento direto do navegador?+

Não deve. A key da instância autentica todas as chamadas e não pode ficar exposta no front. O navegador chama o seu backend, que conhece a key do cliente e fala com a API. O front só recebe o código e o status.

Como sei que o cliente terminou de conectar?+

Configure o webhookConnection pelo PUT /{key}/instance. Quando o pareamento conclui, chega um evento com field connection e status open. Seu backend atualiza o banco e avisa o front por Server-Sent Events, WebSocket ou polling. Como rede de segurança, consulte GET /{key}/instance.

E se a instância já estiver conectada?+

A resposta do pairing-code vem com isConnected true e sem código. Trate como sucesso e pule para o próximo passo do onboarding. A API não gera código para sessão já pareada, porque isso derrubaria a conexão existente.

O cliente vai ver a marca WAME no onboarding?+

Não precisa. A tela, as instruções e o código aparecem dentro do seu produto. No celular do cliente, a conexão aparece na lista de Aparelhos conectados como uma sessão de navegador. É um fluxo white label.

Continue lendo