Proxies
De qual endereço cada sessão do WhatsApp sai — proxies ISP brasileiros de IP fixo, poucos por IP, comprados pela API do IPRoyal e controlados pelo backoffice.
Cada sessão WAHA conecta ao WhatsApp atrás de um proxy (config.proxy no
POST /api/sessions). Qual endereço é decisão de produto, não topologia de
infra (AD-002): o WhatsApp lê o IP ao julgar se um login parece de uma pessoa.
Por isso o vínculo IP ↔ sessão é dado nosso, no domínio domains/proxies.
Por que ISP residencial brasileiro, de IP fixo
O consenso que a pesquisa levantou (research/2026-09-11-proxies-residenciais-br):
- IP dedicado para poucas contas; nunca datacenter; nunca IP estrangeiro com número brasileiro;
- sem rotação: IP que muda derruba todo pareamento daquele endereço de uma vez;
- não existe número público de sessões por IP; 10 por IP é agressivo → começar com 3–5 e medir.
O código impõe isso: só se compra o produto isp_dedicated (qualquer outro dá
PRODUCT_NOT_ALLOWED, decidido pelo tipo e não pelo nome), e a adoção recusa
endereço que a sonda classifica como datacenter ou de registro estrangeiro.
Fornecedores
| Fornecedor | Situação |
|---|---|
| IPRoyal (ISP Dedicated, BR) | integrado: IPRoyalSupplier, API de revenda (IPROYAL_API_TOKEN, IPROYAL_API_URL). Primeira compra em 2026-09-29. |
| Decodo, Oxylabs | recomendados pela pesquisa como fornecedor principal + segunda fonte, mas não vendem ISP dedicado por API (só painel). Sem código. |
Em aberto
A pesquisa recomenda manter dois fornecedores para trocar IP queimado sem
parar o pool. Hoje só o IPRoyal existe no código; Decodo/Oxylabs entrariam
como endereços registrados à mão (POST /admin/proxies). A rodada de duas
semanas medindo quedas e bans por fornecedor ainda não aconteceu (nenhum
celular real pareado até 2026-10-18).
Dois contratos separados:
ProxyProvideré a visão do alocador (PROXY_PROVIDER, hoje sólocal). Comlocal, o boot semeia um endereçodirect(capacidade 100 000): a sessão sai do próprio servidor e nada mais muda.ProxySupplieré a loja do operador (catálogo, cotação, compra, extensão, saldo, inventário). Endereços comprados entram no pool por adoção.
Capacidade: PROXY_SEATS_PER_IP
PROXY_SEATS_PER_IP (padrão 3) é a capacidade dada a cada endereço novo,
registrado ou adotado. Cada endereço pode ter o seu valor depois
(PATCH /admin/proxies/{uid} com capacity). No pedido do IPRoyal a pergunta
"Multi-device access" (3, 5 ou 10) é literalmente essa capacidade, e "Fresh IPs
every renewal" tem de ser false.
Alocação
allocateProxy(accountId, seatChannelId) roda dentro de startPairing:
- Se o
seat_channeljá tem binding vivo, reusa o mesmo endereço. - Senão, prefere um endereço onde a família já está, se ainda tem vaga: vários números de uma casa saindo de IPs diferentes é um padrão que o WhatsApp lê mal.
- Senão, o endereço
activemais cheio que ainda cabe (empacotar mantém famílias juntas e deixa endereços inteiros livres). - Nada cabe → 422
NO_PROXY_CAPACITY.
Só endereços active recebem sessão nova.
Em aberto
releaseProxy só é chamado ao queimar um endereço. Um pareamento
desvinculado, cancelado ou que desistiu de reconectar mantém o binding: a vaga
continua ocupando aquele endereço, e o próximo pareamento do mesmo canal volta
para ele.
Modelo (domains/proxies)
| Tabela | Guarda |
|---|---|
proxies | endereço, protocolo (http, socks5, direct), credenciais seladas, IP, ASN, país, kind (isp, mobile, datacenter, direct), capacity, status, validade, pedido de origem |
proxy_bindings | qual sessão (seat_channel) está em qual endereço, e quais estiveram — com released_reason |
proxy_orders | contrato no fornecedor, com idempotency_key e placed_by (operator ou auto) |
proxy_charges | cada débito, em cents, com câmbio do dia |
proxy_budgets | tetos por fornecedor: mensal, por compra, saldo mínimo, cooldown |
provider_balances | o saldo do fornecedor ao longo do tempo |
Status de proxies: probing, active, draining, burned, released.
As rotas nunca devolvem as credenciais.
Sonda, adoção e queima
- Sonda (
POST /admin/proxies/probe): medeconnectMs(o túnel) elookupMsseparados, IP de saída, ASN, registro (RIR), tipo detectado e se oCONNECT g.whatsapp.net:443passa (whatsapp: { ok, failure }). Devolve umprobeTokenválido por 15 min; o registro usa o que foi medido, não o que o formulário mandou. - Adoção (
POST /admin/proxy-supply/adopt): grava comoprobing, sonda e promove aactive, ou marcaburnedcom o motivo:PROBE_FAILED,FOREIGN_REGISTRY,DATACENTER,WHATSAPP_UNREACHABLE. - Drenar (
status: "draining"): para de receber sessão nova; quem está fica. Pode ser desfeito. - Queimar (
status: "burned"): para endereço que o WhatsApp começou a recusar.burnAddressencerra cada pareamento no endereço comodisconnected(proxy_burned), libera os bindings, notifica responsáveis e a pessoa (pairing.reconnect_required) e devolve as contas à fila de ingest na frente dos novatos. Ver o runbook.
Saldo e validade
- Não existe endpoint de recarga no IPRoyal: o saldo pré-pago é o teto físico de gasto. Regra da pesquisa: saldo sempre, cartão nunca; auto-extend pago pelo saldo.
GET /admin/proxy-supply/balancesmostra saldo, renovações automáticas previstas (estimadas pelo preço do plano × 1,25, porque a API não informa o preço real),shortfalle quantas sessões estão em risco. Perguntar de novo ao fornecedor tem piso de 60 s.- Validade com sessões dentro é incidente:
criticala 3 dias ou menos,warningaté 14. Todas voltariam de outro IP ao mesmo tempo.
Em aberto
proxy_budgets.auto_purchase_enabled existe, mas nenhum worker compra
sozinho: toda compra hoje é feita por um operador.
Rotas de admin
| Rota | Para quê |
|---|---|
GET /admin/proxies | pool, do mais cheio ao mais vazio |
POST /admin/proxies | registrar endereço comprado fora (probeToken opcional) |
POST /admin/proxies/probe | testar sem registrar |
PATCH /admin/proxies/{uid} | capacity, label, status (active, draining, burned) |
GET /admin/proxies/{uid}/bindings | quem está no endereço |
GET /admin/proxy-supply/{catalog,inventory,orders,costs,budgets,balances} | loja, contratos, custo, tetos, saldo |
POST /admin/proxy-supply/{quote,orders,adopt} | cotar, comprar o que a cotação travou, adotar |
POST /admin/proxy-supply/orders/{uid}/extend/quote, …/extend | renovar |
PUT /admin/proxy-supply/budgets/{provider} | definir teto |
No backoffice: Infraestrutura → Proxies, Compras e Orçamento.
Fontes
backend/src/domains/proxies/(index.ts,tables/index.ts,use-cases/{allocate,probe,supply,supply-orders}.ts,providers/{contract,registry,supply}.ts)backend/src/domains/channels/use-cases/{start-pairing,burn-address}.tsbackend/src/http/routes/admin/{proxies,proxy-supply,probe}.route.ts,backend/src/config/env.ts,backend/.env.exampleapps/backoffice/src/components/{proxies,proxy-supply}/- Vault: AD-002, AD-016,
research/2026-09-11-proxies-residenciais-br(e adendo 2026-09-29),problems/2026-09-29-sonda-de-proxy-dois-bugs,_state,specs/2026-10-18-redesign-blueprint(estágio real)
Pareamento
Como um WhatsApp vira um dispositivo vinculado — por código no celular da pessoa (D3) ou por QR na tela do responsável —, com gates, checagem do número e reconexão.
Atualizar o WAHA
Onde a versão do WAHA é fixada, o que o nosso código assume dele e o roteiro para trocar a imagem sem derrubar os pareamentos.
