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):
| Requisito | Como chegar lá | Erro se faltar |
|---|---|---|
| Consentimento dado | Consentimento | 422 CONSENT_REQUIRED |
| Vínculo não refutado | Checagem de vínculo | 422 RELATIONSHIP_REFUTED |
| Vaga ativa | POST /seats/{uid}/activate | 422 SEAT_NOT_ACTIVE |
| WhatsApp habilitado na vaga | POST /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ável | Dados da pessoa | 422 DEPENDENT_PHONE_IS_GUARDIAN |
| Endereço de proxy com vaga | Operação | 422 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:
POST /seats/{uid}/pairingcom{ method: "qr" }abre um pareamento novo (só owner);POST /seats/{uid}/pairing/challenge?method=qrassume um pareamento que estava no código da pessoa: mesma sessão, QR nesta tela, e o código para de valer. Depois que alguém já digitou ou escaneou, responde422 PAIRING_NOT_SWITCHABLE.
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.
| Status | O que mostrar |
|---|---|
starting / challenge | Esperando o QR ou código (challenge.kind: qr, code, passkey_confirm ou code_on_dependent_phone) |
passkey | O 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 |
connecting | Código digitado ou QR lido |
paired | Conectado, e o número confirmado é o da pessoa. A primeira conexão começa o teste grátis (trial.started) |
degraded | Caiu. Nova tentativa em 30 s, 2 min e 10 min (nextRetryAt, reconnectAttempts) |
disconnected | Desistiu de reconectar. Notificação crítica pairing.disconnected |
cancelled | Conectou outro número: o pareamento é desfeito (pairing.identity_mismatch, notificação crítica) |
revoked | Desvinculado (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:
- Espera antes da tentativa
- Tempo desde a queda
| tentativa | Espera antes da tentativa | Tempo desde a queda |
|---|---|---|
| 1ª tentativa | 0,5 min | 0,5 min |
| 2ª tentativa | 2 min | 2,5 min |
| 3ª tentativa | 10 min | 12,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.
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.tsbackend/src/http/routes/consents/pairing.route.tsbackend/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.tsbackend/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)
Consentimento
A atestação do responsável, o link de consentimento, a página pública, a evidência por selfie e áudio, a recusa e a parada pela própria pessoa.
Pausar e parar
Pausa feita pela família e parada feita pela própria pessoa - quem pode, o que para, o que é apagado, como volta e quais eventos e notificações saem.
