Skip to content

Migrar desde whatsapp-web.js

whatsapp-web.js es la librería Node.js más popular para integrar WhatsApp (~17k stars). Suele usarse embebida en una app Node con LocalAuth (filesystem) o RemoteAuth (cualquier store).

La migración a qrsgen tiene sentido cuando:

  • Tu app Node embebía la librería y operar varios números se vuelve complicado.
  • Necesitas features que whatsapp-web.js no tiene (outbox, BanWatcher, audit log inmutable).
  • Quieres separar el bridge de tu app de negocio.

Estructura de datos en whatsapp-web.js

A diferencia de Evolution o qrsgen, whatsapp-web.js no impone un schema relacional propio. Es una librería embebida en TU app; tú decides qué guardas y cómo.

Lo único que sí define son dos estrategias de auth state:

LocalAuth (filesystem, default)

.wwebjs_auth/
└── session-<clientId>/
    ├── Default/
    │   ├── Cookies, IndexedDB/, Local Storage/, ...   (perfil Chrome)
    │   └── ...
    └── ...

Es un directorio Chrome/Chromium serialized. Cada clientId es una sesión independiente. Puppeteer lo restaura entre arranques de tu app.

RemoteAuth (store externo)

Guarda el mismo bundle en MongoDB / S3 / lo que configures:

new RemoteAuth({
  store: new MongoStore({ ... }),
  clientId: 'support',
  backupSyncIntervalMs: 300000,
})

El "schema" es opaco — un blob binario por sesión. No serializable de forma legible.

Tablas en TU app (las que tú hayas creado)

Como whatsapp-web.js no fuerza nada, lo típico es que tu app Node tenga algo como:

-- Ejemplo de schema en app embebiendo wajs
clients (
  id PRIMARY KEY,
  client_id      -- el wajs clientId
  webhook_url
  inbox_id       -- mapeo a tu downstream
  ...
)

messages_log (
  id, client_id (FK)
  remote_jid, content, direction
  ...
)

Esto es 100% custom — tú sabes qué tienes. Lo que importa para qrsgen es la lista de client_id para regenerarlos como instancias.

Lo que NO se puede migrar de wajs

  • Directorio .wwebjs_auth/: es estado Puppeteer/Chrome. qrsgen usa whatsmeow (sin browser), distinto formato totalmente.
  • RemoteAuth stores: incluso si exportas el blob, qrsgen no sabe leerlo. Mismo problema.

Re-pairing obligatorio.

Mapeo conceptual

whatsapp-web.js qrsgen
clientId (por session) name de la instancia
LocalAuth({clientId}) / RemoteAuth Sesión persistida en whatsmeow_* (Postgres) — re-pairing obligatorio
client.on('message', ...) POST a events_webhook_url
client.sendMessage(jid, body) POST /api/instances/:name/webhook con message_type=outgoing
Auth manual en código QRSGEN_API_TOKEN + Bearer

Receta

1. Inventory desde tu app Node

Si tu app gestiona N clients, exporta la lista:

// migrate-export.js (ejecutado dentro de tu app)
const fs = require('fs');
const clients = [/* tu array de clientIds y metadata */];

const plan = {
  instances: clients.map(c => ({
    name: c.clientId,
    events_webhook_url: process.env.NEW_WEBHOOK_URL,
    inbox_id: c.inboxId || null,
    owner_tag: c.tenantId || 'migrated-from-wajs',
  })),
};

fs.writeFileSync('/tmp/qrsgen-plan.json', JSON.stringify(plan, null, 2));
console.log(`Exportadas ${plan.instances.length} instancias`);

2. Aplicar el plan en qrsgen

QRSGEN_URL=http://qrsgen:3100 \
QRSGEN_TOKEN="$QRSGEN_API_TOKEN" \
python3 tools/migrate/bulk-provision.py /tmp/qrsgen-plan.json

3. Re-pairing manual

Igual que en cualquier otra migración WhatsApp: los usuarios reescanean sus QRs. La sesión LocalAuth que whatsapp-web.js guardaba en .wwebjs_auth/ no es compatible con whatsmeow (qrsgen).

4. Refactor de tu app

El cambio más grande no es de datos sino de arquitectura:

// Antes (whatsapp-web.js embebido)
const { Client, LocalAuth } = require('whatsapp-web.js');
const client = new Client({ authStrategy: new LocalAuth({clientId: 'main'}) });
client.on('message', msg => handleIncoming(msg));
await client.sendMessage('34600000000@c.us', 'Hola');

// Después (qrsgen como bridge externo)
const httpx = ...
await fetch('http://qrsgen:3100/api/instances/main/webhook', {
  method: 'POST',
  body: JSON.stringify({
    event: 'message_created',
    message_type: 'outgoing',
    content: 'Hola',
    conversation: { id: 1, meta: { sender: { identifier: '34600000000@s.whatsapp.net' }}},
    id: Date.now(),
    private: false,
  }),
  headers: { 'Content-Type': 'application/json' },
});

// Y recibes incoming via webhook receiver de Express/Fastify/etc:
app.post('/qrsgen-events', async (req, res) => {
  const ev = req.body;
  if (ev.event === 'qr_generated') { /* mostrar al user */ }
  if (ev.event === 'connected')    { /* opcional: notificar */ }
  res.json({ ok: true });
});

5. JID format differences (cuidado)

  • whatsapp-web.js: <phone>@c.us (legacy) o <phone>@s.whatsapp.net.
  • qrsgen / whatsmeow: SIEMPRE <phone>@s.whatsapp.net para números. Los @c.us son auto-convertidos por whatsmeow al @s.whatsapp.net internamente.

Si tu código tiene @c.us hardcoded, sustitúyelo:

// Antes
client.sendMessage(`${phone}@c.us`, content);

// Después (en payload qrsgen)
{ ..., conversation: { meta: { sender: { identifier: `${phone}@s.whatsapp.net` }}}}

6. Ventajas inmediatas tras la migración

  • Outbox automático: si tu app falla cuando intenta enviar, qrsgen lo encola. Antes perdías el mensaje.
  • Sin gestión de re-conexión: whatsmeow + outbox de qrsgen lo manejan. Quitas todo el código de retry/reconnect de tu app.
  • Multi-instance trivial: añade más instancias sin levantar nuevos procesos Node.
  • Persistencia sin filesystem: te quitas la complejidad de backupear .wwebjs_auth/.

Ejemplo: migrar una app con 5 clients

Si tu app Node tiene 5 clients hardcoded:

const clientIds = ['support', 'sales', 'tech', 'billing', 'marketing'];

Plan JSON:

{
  "instances": [
    {"name": "support",   "events_webhook_url": "https://app.example.com/qrsgen-events", "owner_tag": "main"},
    {"name": "sales",     "events_webhook_url": "https://app.example.com/qrsgen-events", "owner_tag": "main"},
    {"name": "tech",      "events_webhook_url": "https://app.example.com/qrsgen-events", "owner_tag": "main"},
    {"name": "billing",   "events_webhook_url": "https://app.example.com/qrsgen-events", "owner_tag": "main"},
    {"name": "marketing", "events_webhook_url": "https://app.example.com/qrsgen-events", "owner_tag": "main"}
  ]
}

Aplicar:

QRSGEN_URL=http://qrsgen:3100 QRSGEN_TOKEN=... \
  python3 tools/migrate/bulk-provision.py plan.json

Tus 5 clients en qrsgen, esperando QR. Tras re-pairing, tu app Node ya no necesita whatsapp-web.js — solo hace fetch a qrsgen.

Glosario

whatsapp-web.js: librería Node.js que implementa el protocolo WhatsApp Web vía Puppeteer + Chromium (más pesado que whatsmeow). Stars en GitHub: ~17k.

LocalAuth: estrategia de whatsapp-web.js que guarda la sesión en filesystem (.wwebjs_auth/clientId/). No portable a otros clientes.

RemoteAuth: estrategia donde la sesión se guarda en un store externo (MongoDB, S3). Más flexible pero igual de no-portable a otro cliente.

clientId: identificador de una sesión en whatsapp-web.js. Equivalente al name de instancia en qrsgen.

Puppeteer / Chromium: stack que whatsapp-web.js usa para hablar con WhatsApp Web (controla un navegador headless). Mucho más pesado que whatsmeow (sin browser).

JID format: WhatsApp tiene dos formatos para identificar números: @c.us (legacy) y @s.whatsapp.net (estándar Multi-Device). qrsgen usa el segundo siempre.