Skip to content

Arquitectura — Visión general

TL;DR

qrsgen es un bridge en Go que mantiene WebSockets contra los servidores de WhatsApp y traduce ese protocolo binario a/desde una API HTTP REST. Una instancia qrsgen = una sesión WhatsApp = un número. Un solo proceso gestiona N instancias concurrentes en goroutines independientes.

Diseñado para:

  • Vivir en una overlay LAN detrás de cualquier orquestador (n8n, una app custom, un CRM, etc.). Sin DNS público.
  • Reanudarse sin pérdida durante restarts cortos (≤5 min) gracias al outbox persistido.
  • Detección proactiva de ban risk (velocity, diversity, delivery ratio).
  • Multi-tenant ligero vía owner_tag libre + agregado de usage por mes.
  • Audit log inmutable con triggers que rechazan UPDATE/DELETE.

¿Cómo recibe WhatsApp si qrsgen no tiene IP pública?

Pregunta frecuente. Respuesta: el WebSocket TCP se inicia desde qrsgen hacia Meta (outbound). Una vez establecido, los mensajes viajan en ambas direcciones por la misma conexión TCP. Es el mismo patrón que el navegador con WhatsApp Web: tu portátil no tiene IP pública y recibe mensajes sin problema porque tu navegador abrió la conexión.

  • qrsgen → Meta: SYN saliente, NAT/firewall lo permite.
  • Meta → qrsgen: respuestas sobre la conexión ya establecida, NAT stateful las permite.

→ No requiere DNS público, ni puerto abierto desde internet, ni IP pública. Solo egress permitido al :443 hacia rangos Meta (mantenidos en firewall.sh como allowlist iptables).

Vista 10.000m

flowchart LR
    subgraph downstream[Tu orquestador / app / CRM]
      DS[downstream]
    end

    subgraph qrsgen[qrsgen process]
      API[Echo HTTP API<br/>:3100<br/>Bearer + HMAC opcional]
      MGR[Manager<br/>N instancias]
      OUTBOX[Outbox<br/>5 min TTL]
      BAN[BanWatcher<br/>velocity / diversity / delivery]
      USE[Usage Tracker<br/>flush 60s]
      AUD[Audit log<br/>append-only]
      WMEOW[wameow.Conn × N<br/>WebSocket por instancia]
    end

    PG[(Postgres<br/>bridge_*<br/>whatsmeow_*)]
    META[(Meta servers)]

    DS -->|POST /webhook<br/>POST /instances<br/>GET /usage| API
    API --> MGR
    API --> OUTBOX
    API --> AUD
    MGR --> WMEOW
    MGR --> BAN
    MGR --> USE
    OUTBOX -. drainer .-> MGR
    WMEOW <-->|WebSocket TLS<br/>outbound 443| META
    qrsgen <--> PG
    MGR -->|lifecycle webhook| DS

Capas del binario

┌─────────────────────────────────────────────────────────────────────┐
│  cmd/server/main.go                                                 │
│  - Composition root: cablea manager, outbox, banwatch, usage, audit │
│  - Echo HTTP server con middleware Bearer + HMAC + RequestID        │
│  - /api/health, /api/instances/*, /api/usage, /api/audit, /metrics  │
└──────┬──────────────────────────────────────────────────────────────┘
   ┌───┼───────────┬────────────┬────────────┬─────────────┬─────────┐
   ▼   ▼           ▼            ▼            ▼             ▼         ▼
 config bridge   manager     outbox       banwatch       usage    audit
 (env)  in/out   N instances queue 5min   velocity etc   daily    immut
                              + drainer    + endpoint     + flush  triggers
   │                │             │            │             │       │
   └────────────────┴─────────────┴────────────┴─────────────┴───────┘
                       wameow.Conn × N — WebSocket por instancia
                       Postgres (bridge_* + whatsmeow_*)

Glosario

Bridge: programa intermediario entre dos protocolos. qrsgen traduce entre el protocolo binario WhatsApp Web y HTTP REST.

Multi-Device: protocolo de WhatsApp que permite varios clientes vinculados al mismo número (hasta 4 dispositivos + el principal).

Overlay LAN: red privada virtual entre containers de un mismo Docker Swarm. Los servicios se ven entre sí por nombre sin pasar por internet.

Composition root: punto único del programa donde se construyen e inyectan todas las dependencias entre módulos. En qrsgen es main.go.

WebSocket: protocolo de conexión TCP persistente bidireccional sobre HTTP/TLS. qrsgen mantiene uno por instancia contra los servidores de Meta.

Outbound (conexión): el cliente inicia la conexión TCP hacia el servidor. Permitido por NAT/firewall sin necesidad de IP pública. Patrón opuesto a "inbound" (servidor escuchando un puerto abierto).

NAT stateful: tipo de NAT que recuerda conexiones establecidas y permite tráfico de respuesta sin necesidad de port-forwarding. Es lo estándar en routers domésticos y firewalls de VPS.