SuperdindaDocs

Pareamento

Como o app do responsável conecta o WhatsApp da pessoa protegida - pré-requisitos, código no celular da pessoa ou QR na tela, estados, eventos e erros.

Esta página mostra o pareamento do lado do app: rotas, estados e eventos. O lado do provedor (WAHA com GOWS, sessões, proxies, código por número) está em Instances → Pareamento.

Antes de parear

O pareamento só abre quando tudo isto vale (assertPairingAllowed e startPairing):

RequisitoComo chegar láErro se faltar
Consentimento dadoConsentimento422 CONSENT_REQUIRED
Vínculo não refutadoChecagem de vínculo422 RELATIONSHIP_REFUTED
Vaga ativaPOST /seats/{uid}/activate422 SEAT_NOT_ACTIVE
WhatsApp habilitado na vagaPOST /seats/{uid}/channels/{channel} com channel = whatsapp (só owner, contrata o adicional pelo preço de hoje)422 CHANNEL_NOT_ENABLED
Nenhum pareamento aberto—422 PAIRING_EXISTS
Telefone da pessoa diferente do de um responsávelDados da pessoa422 DEPENDENT_PHONE_IS_GUARDIAN
Endereço de proxy com vagaOperação422 NO_PROXY_CAPACITY

GET /seats/{uid}/consent responde pairingAllowed e blockedBy antes de você tentar. GET /seats/{uid}/channels lista os canais da vaga com o pareamento de cada um.

Dois caminhos

1. Código no celular da pessoa (D3, o caminho principal). Depois do sim, a própria página de consentimento chama POST /consent-requests/{token}/pairing e mostra um código de 8 caracteres. A pessoa digita em WhatsApp → Aparelhos conectados → Conectar com número de telefone. O código nunca aparece pros responsáveis: no app deles, o desafio chega como { kind: "code_on_dependent_phone", expiresAt }, e a tela diz que a pessoa está digitando o código. A página da pessoa acompanha por GET /consent-requests/{token}/events (veja Tempo real).

2. QR na tela do responsável. Pra quando estão juntos:

Sem ?method, a mesma rota busca um QR ou código novo sob demanda. Os workers também renovam sozinhos e empurram por SSE (pairing.challenge). O { method: "code" } do responsável ainda existe (mostra o código pra ele), mas o redesign não usa.

Estados

stateDiagram-v2
  [*] --> starting
  starting --> challenge: QR ou código chegou
  challenge --> passkey: WhatsApp pede pra conferir o número
  passkey --> challenge: desafio novo
  challenge --> connecting: código digitado ou QR lido
  passkey --> connecting: a pessoa disse que bate
  connecting --> paired: número confirmado
  connecting --> cancelled: outro número conectou
  passkey --> cancelled: a pessoa disse que não bate
  challenge --> cancelled: códigos esgotados ou recomeço
  challenge --> disconnected: o desafio falhou
  paired --> degraded: caiu
  degraded --> paired: reconectou
  degraded --> disconnected: a 3ª tentativa falhou
  paired --> revoked: desvinculado, parada ou reset
  cancelled --> [*]
  disconnected --> [*]
  revoked --> [*]

Qualquer pareamento aberto também pode terminar em revoked (desvincular, parada pela pessoa, reset no backoffice); o diagrama mostra só a partir de paired pra não poluir.

StatusO que mostrar
starting / challengeEsperando o QR ou código (challenge.kind: qr, code, passkey_confirm ou code_on_dependent_phone)
passkeyO WhatsApp mostrou um número no celular da pessoa e pergunta se bate: POST /seats/{uid}/pairing/passkey com { matches }. false encerra como passkey_refused
connectingCódigo digitado ou QR lido
pairedConectado, e o número confirmado é o da pessoa. A primeira conexão começa o teste grátis (trial.started)
degradedCaiu. Nova tentativa em 30 s, 2 min e 10 min (nextRetryAt, reconnectAttempts)
disconnectedDesistiu de reconectar. Notificação crítica pairing.disconnected
cancelledConectou outro número: o pareamento é desfeito (pairing.identity_mismatch, notificação crítica)
revokedDesvinculado (DELETE /seats/{uid}/pairing, sempre 204), parado pela pessoa ou resetado no backoffice

Quando cai, o worker tenta de novo três vezes, esperando mais a cada vez. A 3ª tentativa sai cerca de 12 min e meio depois da queda; se ela também falhar, o pareamento vai pra disconnected e o responsável recebe a notificação crítica:

Reconexão depois de uma queda
  • Espera antes da tentativa
  • Tempo desde a queda
Reconexão depois de uma queda
tentativaEspera antes da tentativaTempo desde a queda
1ª tentativa0,5 min0,5 min
2ª tentativa2 min2,5 min
3ª tentativa10 min12,5 min

RECONNECT_DELAYS_MS em backend/src/domains/channels/use-cases/apply-provider-state.ts: 30 s, 2 min e 10 min. Se a 3ª também falhar, o pareamento vai pra disconnected.

GET /seats/{uid}/pairing devolve o pareamento aberto. Depois de conectar, GET /seats/{uid}/pairing/avatar serve a foto de perfil da própria pessoa (bytes, 404 se não houver: use as iniciais). Foto de contato nunca é servida, porque essas pessoas não consentiram.

Abrindo um QR e seguindo pelo SSE
const { data: pairing } = await api(
  `/seats/${seatUid}/pairing`,
  {
    method: "POST",
    body: JSON.stringify({ channel: "whatsapp", method: "qr" }),
  },
  token,
);
render(pairing.challenge); // pode vir null: o QR chega por SSE

source.addEventListener("event", (e) => {
  const ev = JSON.parse(e.data);
  if (ev.seatUid !== seatUid) return;
  if (ev.type === "pairing.challenge") render(ev.payload.challenge);
  if (ev.type === "pairing.paired") done();
  if (ev.type === "pairing.status")
    showStatus(ev.payload.status, ev.payload.nextRetryAt);
});

Em aberto

  • O pedido de reconexão pra própria pessoa (G-26) ainda não existe. Quando o pareamento cai de vez, só o responsável recomeça.
  • O código do WhatsApp é tratado como válido por 60 s, porque o WAHA não documenta o prazo real (nota da frente k-pairing).

Fontes

  • backend/src/http/routes/channels/pairing.route.ts, enable.route.ts, schemas.ts
  • backend/src/http/routes/consents/pairing.route.ts
  • backend/src/domains/channels/use-cases/start-pairing.ts, dependent-pairing.ts, refresh-challenge.ts, confirm-passkey.ts, apply-provider-state.ts, cancel-pairing.ts, capture-avatar.ts
  • backend/src/domains/subscriptions/use-cases/start-trial.ts
  • Notas da frente k-pairing (k-pairing-api.md)
  • .specs/features/redesign/api-gaps.md (G-23, G-26)
  • AD-010, AD-019 (D3)

On this page