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. RemoteAuthstores: 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.netpara números. Los@c.usson auto-convertidos por whatsmeow al@s.whatsapp.netinternamente.
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:
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:
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.