Pular para o conteúdo principal
Além do modo companion padrão (vinculação via QR / código de pareamento, como o WhatsApp Web), o zapo pode conectar como um client mobile primário — falando o protocolo do app Android por um socket TCP bruto.
O suporte mobile é estável e funcional. A única coisa que o zapo não fornece é uma API de registro — solicitar um código por SMS/voz, enviar um OTP ou aprovar um takeover. Registrar um número é complexo e requer um telefone físico, então está intencionalmente fora de escopo. Você conecta com um conjunto de credenciais já registrado, e esse caminho é sólido.

Como difere do modo companion

Habilitando o modo mobile

O modo mobile é acionado pela opção mobileTransport (um WaMobileTransportOptions). Sua presença — ou um deviceInfo persistido nas credenciais carregadas — muda o client do transporte WebSocket para o transporte TCP.

WaMobileTransportOptions

WaMobileTransportDeviceInfo

manufacturer, device, osVersion, osBuildNumber, appVersion são obrigatórios; mcc, mnc, localeLanguageIso6391, localeCountryIso31661Alpha2, phoneId, deviceBoard, deviceModelType são opcionais. Um fingerprint estável entre execuções importa — persista-o e reutilize os mesmos valores.

Credenciais

O modo mobile precisa de um conjunto de credenciais já registrado: um WaAuthCredentials com meJid preenchido, platform: 'android' e deviceInfo anexado. Você semeia isso na auth store antes de conectar (por exemplo, importado de um device bundle).
Uma vez que as credenciais com deviceInfo são persistidas, reconexões posteriores rodam automaticamente em modo mobile-primary completo — transporte TCP, formatos de IQ / message id mobile, gating de app-state primary e withholding de placeholder-resend (veja abaixo) tudo deriva do deviceInfo carregado. Você não precisa re-passar mobileTransport em cada construção.

Withholding de placeholder-resend

Quando um device companion falha em decifrar uma mensagem recebida, ele normalmente pede a um peer pareado o plaintext original via um placeholder-resend request. Um telefone primário não tem peer device segurando o plaintext, então uma sessão mobile-primary pula o placeholder request inteiro e cai para um retry receipt comum — o caminho padrão de re-encrypt que o sender já suporta. Isso evita o request expirar silenciosamente e a mensagem ser dropada.

Eventos de registro

Enquanto sua sessão mobile está conectada, você é notificado quando alguém tenta registrar seu número em outro dispositivo — um sinal relevante para segurança, exposto como estes eventos:
Esses eventos são informativos — o zapo os expõe, mas intencionalmente não disponibiliza métodos para enviar um código ou responder a um takeover. O provisionamento de um número é feito em um telefone real; traga as credenciais resultantes para o zapo e conecte.

Vínculo de email

Mobile-only client.email (WaEmailCoordinator) vincula e verifica um endereço de email na conta — um fator de recuperação/login. É mobile-only: todo método lança erro em uma conexão Web/companion.

Hospedando dispositivos companheiros

Apenas mobile-primary Uma sessão companion vive do outro lado de um QR / código de pareamento, vinculada por um telefone real. Quando o zapo é o telefone, os papéis se invertem: esta sessão primary pode hospedar companions — vincular uma aba do WhatsApp Web, revogá-la, listar o que está conectado e reconciliar contra a lista de dispositivos do servidor. O coordinator fica em client.mobile (WaMobileCoordinator).
client.mobile requer uma sessão mobile-primary. Ler o getter em uma conexão Web/companion retorna o coordinator, mas os métodos de link/revoke/publish lançam client.mobile requires a mobile-primary session (…) antes de tocar na rede. reconcileCompanions() vira um no-op em sessões não-primary.

Métodos

Eventos

Atividade de companion-host aparece no client:

Bootstrap gates cuidados por você

Todo companion que um telefone WhatsApp vincula hoje espera três sinais no pair-time. O zapo os fornece para que o companion não se remova sozinho após o QR:
  • Client-props de LID migration — um primary LID-native declara isChatDbLidMigrated e isSyncdPureLidSession no elemento <client-props> para que o companion rode setIsLidMigrated no pair-time e não se remova sozinho por conta da sua blocklist endereçada por LID.
  • History-sync INITIAL_BOOTSTRAP — o primary envia a notificação inicial de history-sync como uma peer message; sem ela, o companion se remove sozinho com HistorySyncTimeout.
  • setting_pushName semeado — o nome de exibição do próprio primary é semeado na collection critical_block do app-state, uma vez por sessão, para o critical bootstrap do companion completar.
Todos os três são automáticos; nenhuma chamada de método necessária.

Persistência

O estado do epoch ADV (rawId, currentKeyIndex, companions rastreados) precisa persistir entre reinícios — reusar um índice de chave companion já emitido quebra dispositivos previamente vinculados. Wire um CompanionHostPersistence em WaClientOptions.companionHost.persistence e o zapo carrega/salva transparentemente. Uma implementação file-backed vem embarcada com a lib:
Sem hook de persistência o epoch é apenas em memória — bom pra smoke test, inseguro pra relink em produção. No restart o primary re-emitiria o key index 1 pra um novo companion enquanto o companion previamente linkado ainda o carrega, quebrando decrypt de sessão no device mais antigo.

Backend custom

O contrato é dois métodos sobre CompanionHostEpochState:
Aponte pra qualquer store que você já roda. O estado é pequeno (header do epoch + uma linha por companion linkado), então não há necessidade de um domínio de core-store dedicado. Exemplo contra better-sqlite3:
O save roda a cada mudança de estado por linkCompanion / revokeCompanion / reconcileCompanions, então envolva a escrita multi-statement numa transaction pra manter atomicidade. load é chamado uma vez durante o wire-up do coordinator.

Exemplo trabalhado

Funcionalidades padrão continuam valendo

Uma vez conectado no modo mobile, o resto da API permanece inalterado — client.message, client.group, eventos, stores, etc. funcionam todos da mesma forma. A única diferença é o transporte e o modelo de auth/identidade.
Última modificação em 12 de julho de 2026