Número banido no WhatsApp: como migrar usuários para um novo

O WhatsApp baniu o número do seu agente: os chats não se movem e o novo número não pode escrever primeiro. Um passo a passo com código de webhook para migrar usuários.

Uma fila de balões de conversa atravessando uma ponte estreita, de um celular apagado para um verde e aceso.

O essencial

  • Quando o WhatsApp bane um número da whasp, você recebe um substituto sem custo, com o mesmo label, perfil e período pago, anunciado por um evento de webhook number.replaced, mas nenhum chat se move.
  • O novo número não pode escrever primeiro para ninguém (409 chat_not_open), então todo usuário precisa escrever para ele, e o convite deve viajar por um canal que é seu, como e-mail, push ou um banner no app.
  • Envie a cada usuário um link montado com new_number.wa_link e uma mensagem pré-preenchida com um código de uso único, e convide em lotes de cerca de 25 por dia, porque um número aceita 40 novos chats a cada 24 horas.
  • Associe o histórico ao seu próprio id de usuário, e não ao número, para reconhecer quem volta pelo telefone ou pelo código e continuar a conversa de onde ela parou.
  • Prepare-se antes do primeiro banimento: um segundo canal coletado no cadastro, um link de redirecionamento estável, esse link no recado do perfil e um handler de number.replaced já testado.

Quando o WhatsApp bane um número, os chats não se movem: o substituto começa vazio e não pode mandar mensagem primeiro para ninguém. Para migrar os seus usuários, avise por um canal que é seu, envie a cada um um link wa.me para o novo número com uma mensagem pré-preenchida e reconheça-os quando escreverem, para restaurar o histórico a partir do seu próprio armazenamento.

O que acontece quando o WhatsApp bane o número do seu agente?

Quando o WhatsApp bane um número da whasp em definitivo, a whasp o aposenta e passa você para um número pronto, sem custo. O substituto tem o mesmo label, o mesmo perfil (nome, recado, foto) e o mesmo período pago. Nada mais é transferido. Nenhum chat se move, e o novo número não tem nada do histórico antigo.

Seu webhook recebe um evento number.replaced com o número antigo e o objeto do novo número, incluindo o wa_link. Aqui está o evento, resumido:

{
  "id": "evt_9dK3mQ2vLx",
  "type": "number.replaced",
  "created_at": "2026-10-18T19:00:00Z",
  "data": {
    "old_number": "+15555550100",
    "new_number": {
      "number": "+15555550102",
      "wa_link": "https://wa.me/15555550102",
      "label": "ava",
      "status": "active",
      "profile": { "name": "Ava", "about": "Sua amiga de IA", "synced": true },
      "paid_until": "2026-11-17T19:00:00Z"
    }
  }
}

Um evento number.status_changed também pode chegar, quando a saúde do número antigo passa a critical. A ordem dos eventos não é garantida, então não dependa dela. O número banido não responde mais a ninguém. Para entender por que isso aconteceu, leia por que o WhatsApp bane números. Para a primeira hora depois de um banimento, incluindo a página do próprio WhatsApp sobre banimento de contas, veja o que fazer quando um número é banido.

O novo número pode mandar mensagem primeiro para os seus usuários?

O novo número não pode mandar mensagem para quem não escreveu para ele. Um envio para essa pessoa retorna 409 chat_not_open. Isso vale para todo número da whasp, inclusive os substitutos, e é de propósito: mensagens frias são o que bane números pessoais, e um número novo que abrisse centenas de chats no primeiro dia estaria convidando o próximo banimento.

Então a migração precisa começar pelo usuário. Ele precisa tocar em um link e escrever. Isso faz do convite a parte difícil, porque ele não pode vir do chat banido do WhatsApp nem do novo número. Precisa viajar por um canal que é seu. Quando o usuário escreve, os envios sem from saem do novo número, porque a API usa o número para o qual a pessoa escreveu por último.

Qual é o passo a passo para migrar os usuários?

O passo a passo tem sete etapas, e o código abaixo cobre as seis primeiras.

  1. Verifique o evento e deduplique pelo id dele (eventos podem chegar duas vezes, e a especificação Standard Webhooks recomenda usar webhook-id como chave de idempotência).
  2. Encontre os usuários afetados: aqueles cujo chat vivia no old_number. Salve o número em cada usuário a partir de message.received, em que message.number diz qual número recebeu a mensagem.
  3. Monte um link por usuário: new_number.wa_link mais ?text= (o formato click-to-chat do WhatsApp) com uma mensagem curta que leve um código de uso único.
  4. Envie o link por um canal seu: um banner no app, uma notificação push ou um e-mail.
  5. Convide em lotes, começando pelos mais recentemente ativos.
  6. Reconheça a pessoa quando ela escrever, restaure o histórico e faça o agente dizer que o número mudou.
  7. Lembre uma vez e pare. Depois de uma semana, mande um único lembrete a quem não voltou. O resto volta quando quiser o agente de novo.

Como tratar o evento e enviar os convites?

Verifique a assinatura no corpo bruto, deduplique pelo id do evento e comece os convites. Este handler reaproveita a verificação do Standard Webhooks que está na documentação da whasp.

import { randomBytes } from "node:crypto";
import { Webhook } from "standardwebhooks";

type User = { id: string; phone: string; number: string; lastActive: number; invited?: boolean };

declare const users: Map<string, User>; // seu banco de dados
declare function notify(user: User, link: string): Promise<void>; // e-mail, push ou banner no app

const wh = new Webhook(process.env.WHASP_WEBHOOK_SECRET ?? ""); // whsec_...
const seen = new Set<string>(); // ids de eventos já tratados
export const codes = new Map<string, string>(); // código de convite -> id do usuário
let moveFrom: { number: string; link: string } | null = null;
const DAILY_INVITES = 25; // abaixo de 80% dos 40 novos chats por dia, com folga para cadastros

export async function POST(request: Request) {
  const body = await request.text(); // o corpo bruto: verifique antes de fazer o parse
  let event: any;
  try {
    event = wh.verify(body, Object.fromEntries(request.headers));
  } catch {
    return new Response("Invalid signature", { status: 401 });
  }
  if (seen.has(event.id)) return new Response(null, { status: 204 });
  seen.add(event.id);

  if (event.type === "number.replaced") {
    const { old_number, new_number } = event.data;
    moveFrom = { number: old_number, link: new_number.wa_link };
    await inviteBatch(); // em produção: responda 2xx primeiro e envie a partir de uma fila
  }
  return new Response(null, { status: 204 });
}

// Rode uma vez quando o evento chegar e depois uma vez por dia, até não sobrar ninguém.
export async function inviteBatch() {
  const target = moveFrom;
  if (!target) return;
  const batch = [...users.values()]
    .filter((u) => u.number === target.number && !u.invited)
    .sort((a, b) => b.lastActive - a.lastActive)
    .slice(0, DAILY_INVITES);
  for (const user of batch) {
    const code = randomBytes(4).toString("hex");
    codes.set(code, user.id);
    const text = encodeURIComponent(`Oi, sou eu de novo (código ${code})`);
    await notify(user, `${target.link}?text=${text}`);
    user.invited = true;
  }
}

O código é aleatório e de uso único, e não o id do usuário, porque uma mensagem pré-preenchida é um texto visível e editável, e ids não devem ficar em links. Troque os Maps e o Set por tabelas do banco de dados.

O tamanho do lote vem dos limites. Um número da whasp aceita 40 novos chats a cada 24 horas e 100 chats ativos em 30 dias, e a saúde dele passa a at_risk quando um limite passa de 80%, o que dá 32 novos chats. Cerca de 25 convites por dia deixam espaço para novos cadastros. Um número com 100 usuários ativos leva quatro dias, e os mais recentemente ativos vão primeiro porque são os que vão perceber.

O que o convite deve dizer?

O convite deve dizer que o agente tem um novo número, mostrar para onde o link leva e soar como uma mensagem sua, não como um alerta. Evite a palavra “banido”; os usuários querem saber que a Ava mudou de número, não o motivo.

Assunto: A Ava tem um novo número

O número antigo da Ava no WhatsApp parou de funcionar. Toque abaixo para continuar de onde você parou. O nome e a foto são os mesmos, e ela se lembra de tudo. [Abrir o chat]

O toque abre o WhatsApp com “Oi, sou eu de novo (código 3f9a1c2e)” pronto para enviar. Envie o link só pelo seu app ou pelo seu domínio. Uma mensagem de um número desconhecido dizendo ser a Ava parece phishing, e o novo número nem consegue enviá-la. Depois da primeira semana, conte três coisas: convidados, retornaram e silenciosos. Os silenciosos recebem um lembrete.

Como reconhecer quem volta e restaurar o histórico?

Identifique a pessoa primeiro pelo telefone e depois pelo código, e então carregue a conversa pelo seu próprio id de usuário. O telefone em chat é o mesmo no novo número e no antigo, então quem volta é encontrado sem código. O código cobre quem escreve de um celular novo.

declare const users: Map<string, User>;
declare const codes: Map<string, string>;
declare function reply(userId: string, text: string, opts: { moved: boolean }): Promise<void>; // seu agente: carrega o histórico pelo id do usuário

export async function onInbound(m: { chat: string; number: string; text: string | null }) {
  const code = m.text?.match(/\(código ([0-9a-f]{8})\)/)?.[1];
  const byCode = code ? users.get(codes.get(code) ?? "") : undefined;
  const user = byCode ?? [...users.values()].find((u) => u.phone === m.chat);
  if (!user) return; // um desconhecido: indique o cadastro

  if (code) codes.delete(code);
  user.phone = m.chat;
  const moved = user.number !== m.number;
  user.number = m.number; // este chat agora vive no novo número
  // moved: faça o agente dizer "Sou eu. Meu número antigo parou de funcionar, e eu ainda me lembro de tudo."
  await reply(user.id, m.text ?? "", { moved });
}

Chame onInbound a partir do seu handler de message.received. A regra de projeto por trás disso: associe o histórico ao usuário, nunca ao número. Se a sua tabela de memória usa o número da whasp como parte da chave, um banimento deixa todas as conversas órfãs. A memória de conversa mostra um esquema que sobrevive a uma migração.

Conte à pessoa o que aconteceu. Um agente que recebe quem voltou com o roteiro de boas-vindas parece um estranho. Uma linha basta: é o mesmo agente, o número antigo parou de funcionar e a conversa continua.

E se o usuário não tiver outro canal?

Se o WhatsApp é o seu único canal com um usuário, você não consegue alcançá-lo. O chat antigo está mudo e o novo número não pode escrever primeiro. Três hábitos diminuem essa lacuna.

Colete um segundo canal no cadastro. Um e-mail ou um login do app custa um campo. É o único caminho de volta confiável.

Publique um link estável. Coloque no seu próprio domínio um redirecionamento que pede a GET /v1/numbers/pick um número saudável na hora do clique. Use esse link onde ele precisa durar, como no seu app, em bios e em QR codes, em vez de um link wa.me bruto.

// GET https://chat.example.com/ava: o link único que você imprime, publica e coloca nos e-mails.
export async function GET() {
  const res = await fetch(
    `https://api.whasp.ai/v1/numbers/pick?text=${encodeURIComponent("Oi, sou eu")}`,
    { headers: { Authorization: `Bearer ${process.env.WHASP_API_KEY}` } },
  );
  if (!res.ok) return new Response("Tente de novo em um minuto.", { status: 503 }); // 409 no_capacity
  const { wa_link } = await res.json();
  return Response.redirect(wa_link, 302);
}

Coloque esse link no perfil. Um substituto mantém o perfil, e o recado aceita até 139 caracteres em ASCII simples por enquanto (ou seja, sem acentos). Uma URL curta ali viaja para todos os substitutos futuros e fica onde os usuários podem ler. Links wa.me e QR codes trata de onde colocá-la.

O que configurar antes do primeiro banimento?

Qualquer número operado como uma pessoa pode ser banido, então prepare-se enquanto tudo está calmo.

  • Teste o handler com um payload de exemplo de number.replaced, incluindo um duplicado e uma assinatura inválida.
  • Planeje para um evento perdido. A whasp tenta entregar até 12 vezes com backoff, depois desiste e descarta o corpo, então ele não pode ser reenviado. GET /v1/webhook/deliveries lista os últimos 7 dias. GET /v1/numbers lista os seus números: um que você não reconhece é o substituto, e os campos status e replaced_by mostram o que aconteceu com o antigo.
  • Rode mais de um número quando puder. Assim, um banimento afeta só os usuários daquele número. Quantos números o seu agente precisa traz a conta.
  • Associe o histórico ao usuário e guarde um telefone em todo registro de usuário.

O que fazer agora?

Adicione o handler de number.replaced esta semana e depois coloque o link estável no seu app e no recado do perfil. A partir de agora, colete um e-mail no cadastro. A API completa está na documentação.

Perguntas

O que acontece com os meus chats quando o WhatsApp bane um número?

Na whasp, nada se move. O número banido é aposentado e você recebe um substituto com o mesmo label, perfil e período pago. O novo número começa sem chats, e o seu sistema guarda o histórico, então você o restaura quando cada usuário escrever.

O número substituto pode mandar mensagem primeiro para os meus usuários?

Não. Um envio para quem nunca escreveu para aquele número retorna 409 chat_not_open, e a regra vale também para números substitutos. Os usuários precisam escrever primeiro, em geral tocando em um link wa.me que você manda por e-mail, push ou dentro do seu app.

Quantos usuários posso migrar para o novo número por dia?

Um número da whasp aceita 40 novos chats a cada 24 horas e 100 chats ativos em 30 dias, e passa a at_risk acima de 80% de um limite. Cerca de 25 convites por dia ficam abaixo disso e deixam espaço para novos cadastros.

Como aviso o novo número aos usuários se só tenho a conversa do WhatsApp?

Você não consegue alcançá-los pelo novo número, e o chat banido fica mudo. Prepare-se antes: colete um e-mail ou um login do app no cadastro e publique um link estável no seu domínio que sempre redirecione para um número saudável.

E se o meu webhook perdeu o evento number.replaced?

A whasp tenta entregar até 12 vezes com backoff, depois desiste e descarta o corpo. Liste os seus números com GET /v1/numbers: um que você não reconhece é o substituto, e os campos status e replaced_by mostram o que aconteceu com o antigo.

Todos os artigos