Skip to content

History import

Backfill 1-30 días de mensajes históricos de WhatsApp al downstream (Chatwoot, etc.) sin necesidad de re-parear la instancia. Disponible desde v0.46.0.

Cuándo usarlo

  • Adoptas qrsgen sobre un número con histórico — quieres que el agente vea los últimos N días de conversaciones que ocurrieron antes de qrsgen.
  • Restauras una instancia desde backup y faltan msgs recientes que no llegaron a Chatwoot.
  • Onboarding de un nuevo cliente — vista inicial poblada en lugar de "no hay mensajes todavía".

Habilitar

Opt-in vía env vars:

QRSGEN_HISTORY_IMPORT_ENABLED=true
QRSGEN_HISTORY_IMPORT_DAYS=7              # 1..30
QRSGEN_HISTORY_IMPORT_RATE_PER_SEC=5      # throttle POSTs al downstream
QRSGEN_RETROACTIVE_NAME_UPDATE=true       # requerido (msg_history tracker)

RETROACTIVE_NAME_UPDATE=true (default) es obligatorio porque el on-demand sync usa el tracker bridge_msg_history para resolver el anchor (último msgID conocido del chat) que WhatsApp necesita como referencia.

Cómo funciona

┌─ qrsgen ─────────────────────┐
│                              │
│ 1. POST /history/import      │
│    {chat: <jid>, count: 50}  │
│                              │
│ 2. Lookup anchor             │
│    FindLastForChat() en      │
│    bridge_msg_history        │
│                              │
│ 3. BuildHistorySyncRequest   │ ──► WhatsApp primary device
│    SendPeerMessage           │     (tu phone)
│                              │
│ 4. WAIT *events.HistorySync  │ ◄── ON_DEMAND response (1-30s)
│    type=ON_DEMAND            │
│                              │
│ 5. Filter por edad           │
│    Sort cronológico          │
│    POST a Chatwoot con       │
│    created_at backdated +    │
│    source_id=WAID            │
│                              │
└──────────────────────────────┘

Endpoints

POST /api/instances/:n/history/import — single chat

Importa el histórico de UN chat específico.

curl -X POST -H "Authorization: Bearer $QRSGEN_API_TOKEN" \
  "$BASE/api/instances/ATC/history/import?chat=34604021705@s.whatsapp.net&count=50"

Query params: - chat=<jid> (requerido) — JID del chat - count=N (default 50, max 200) — msgs a pedir al phone - timeout_sec=N (default 30) — cuánto esperar la respuesta

Response 200:

{
  "instance": "ATC",
  "conversations": 1,
  "messages_seen": 47,
  "messages_kept": 17,
  "posted": 17,
  "skipped": 0,
  "errors": 0,
  "oldest_ts": 1778046834,
  "newest_ts": 1778835233
}

Error 500 si no hay anchor en el tracker:

{"error": "no message anchor for chat ... — qrsgen needs at least one tracked incoming msg from this chat to request more history; wait for an incoming or send a test msg first"}

POST /api/instances/:n/history/import-all — bulk

Itera todos los contactos del inbox y dispara el single-chat import para cada uno. Secuencial (no paralelo) para no estresar al phone.

curl -m 600 -X POST -H "Authorization: Bearer $QRSGEN_API_TOKEN" \
  "$BASE/api/instances/ATC/history/import-all?count_per_chat=50&timeout_per_chat=30"

Query params: - count_per_chat=N (default 50) - timeout_per_chat=N (default 30)

Response 200:

{
  "instance": "ATC",
  "pages": 12,
  "scanned": 171,
  "imported": 3,
  "skipped": 7,
  "no_anchor": 161,
  "errors": 0,
  "total_posted": 45,
  "total_skipped": 0,
  "total_errors": 0
}

Campos: - scanned — contactos totales del inbox - imported — chats con sync exitoso - skipped — identifier no parseable / server no soportado - no_anchor — chats sin msg tracked (no es error, esperado) - errors — timeouts/fallos reales - total_posted — sum de msgs posteados en todos los chats

Tiempos: para 100 chats con anchor × 5s promedio = ~8 min. Usar curl -m 600 para no cortar la conexión.

Limitaciones

Anchor requirement

El feature requiere ≥1 msg incoming tracked en msg_history por chat para tener anchor real que WhatsApp acepte. La cobertura del bulk depende de cuántos chats han recibido al menos un msg desde que QRSGEN_RETROACTIVE_NAME_UPDATE=true está activo.

Mejora prevista (v0.49.0): tabla bridge_chat_anchor que trackea el último WAID de TODOS los chats activos (no solo los con prefix de grupo). Eso sube cobertura del bulk de ~2% al ~100% en producción.

Media files

Hoy NO se importan los binarios — solo placeholders: - 🖼️ [imagen — no importada] - 🎥 [video — no importado] - 🎤 [nota de voz — no importada] - 🎵 [audio — no importado] - 📄 [documento — no importado] - 🟩 [sticker — no importado] - 📍 [ubicación] (este sí, vía formatLocationContent)

Captions de media SÍ se importan con emoji prefix: - 🖼️ foto del verano (caption="foto del verano")

Profundidad del histórico

WhatsApp solo devuelve lo que el phone tiene guardado. Ajustes típicos del phone: 30 / 90 / 180 días / 1 año. Pedir 30 días cuando el phone solo guarda 7 da los 7.

Idempotencia

Cada msg se postea con source_id=WAID:<msgID>. Chatwoot rechaza con 422 si ya existe — qrsgen lo cuenta como duplicate y sigue. Re-correr el import sobre los mismos msgs no duplica.

Robustez (v0.48.1+)

POST a Chatwoot con retry-on-5xx: hasta 3 intentos con backoff 500ms / 1s. Errores 4xx (excepto 422) NO se reintentan — son errores permanentes.

Métricas Prometheus

qrsgen_realtime_events_total{feature="history_import", result="ok"}
qrsgen_realtime_events_total{feature="history_import", result="ds_error"}
qrsgen_realtime_events_total{feature="history_import", result="duplicate"}
qrsgen_realtime_events_total{feature="history_import", result="skip_disabled"}

PromQL útil:

# Throughput de import durante un bulk
rate(qrsgen_realtime_events_total{feature="history_import",result="ok"}[1m])

# Tasa de duplicates (debería ser alta en re-runs idempotentes)
rate(qrsgen_realtime_events_total{feature="history_import",result="duplicate"}[5m])

Receta n8n

Workflow típico para backfillear una agenda completa:

[HTTP Request: POST /history/import-all] → [If: imported > 0]
                                              ↓ true
                                         [Slack notify: "Histórico
                                          de N msgs en M chats
                                          importado"]

Glosario

Anchor: msgID + timestamp que el phone primary usa como referencia temporal para tirar histórico ANTERIOR. Sin anchor real, el phone ignora la request en silencio.

ON_DEMAND: tipo de HistorySync que llega tras un BuildHistorySyncRequest. Otros tipos (INITIAL_BOOTSTRAP, RECENT) llegan automáticamente al parear.

Bulk import: iteración sobre todos los contactos del inbox disparando single-chat imports. Sin paralelismo intencionado — el phone primary procesa una request a la vez.