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.
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.
| Estado | O que a tela mostra | Próximo |
|---|---|---|
pedir_numero | Campo de telefone com DDI | gerando |
gerando | Carregando | mostrando_codigo, conectado ou erro |
mostrando_codigo | Código grande, botão copiar, instruções | conectado ou expirado |
expirado | "O código expirou" e botão para gerar outro | gerando |
conectado | Confirmação e próximo passo do onboarding | fim |
erro | Mensagem clara e opção de tentar de novo | pedir_numero |
Três detalhes de UX que fazem diferença:
- Mostre o código grande, em blocos (
XXXX-XXXX), com botão Copiar. É assim que o painel da própria WAME faz. - 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".
- 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:
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:
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:
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:
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:
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:
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:
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:
| Etapa | Evento |
|---|---|
| Iniciou | Abriu a tela de conexão |
| Gerou código | onboarding_codigo_gerado |
| Conectou | onboarding_conectado |
| Expirou sem conectar | Tela expirado exibida |
| Erro | onboarding_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átisPerguntas 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
Conectar WhatsApp na API sem QR Code: código de pareamento e Passkey
Três jeitos de conectar um número na API não oficial do WhatsApp: QR Code, código de pareamento de 8 caracteres e WAME Passkey. Com cURL, webhooks e reconexão.
Conectar o WhatsApp à API só pelo celular, sem QR Code (código de 8 dígitos)
Só tem o celular? Conecte seu WhatsApp à API sem QR Code: gere o código de 8 dígitos no painel WAME e digite no app. Passo a passo e soluções.
Agente de voz no WhatsApp: latência, interrupção (barge-in) e silêncio
Como deixar um agente de voz no WhatsApp natural: latência, streaming, detecção de fala, interrupção (barge-in), silêncio e eco, com exemplos em Node.js.