Ir al contenido

Historial

Al vincular un número, el teléfono le manda al dispositivo nuevo una parte de su historial. El motor siempre procesa esos lotes por lo que el protocolo necesita (identidades, secretos, nombres), pero no importa los mensajes salvo que la política de la instancia lo diga: la importación es opcional y viene apagada. Encendida, los mensajes viejos llegan como message.received con origin: history, con la misma identidad que uno en vivo (nunca se duplican), y cada importación es un trabajo con su progreso y su resultado. Después puedes pedirle al teléfono mensajes más viejos de un chat, o de varios.

Operación Scope
GET /v1/instances/{id}/history (política y trabajos) y GET …/history/jobs/{job_id} history:read
POST /v1/instances/{id}/history/requests (pedir más de un chat) history:manage
POST /v1/instances/{id}/history/imports (importar varios chats) history:manage
POST /v1/instances/{id}/history/jobs/{job_id}/cancel history:manage
PATCH /v1/instances/{id} con la política (config.historial_*) instances:manage
GET /v1/instances/{id}/messages (lo importado, en el store) messages:read
GET /v1/instances/{id}/events (history.sync, sync.* y los mensajes) events:read

Cada instancia tiene su política; lo que no fija lo toma de Ajustes → Retención e historial:

Campo de config De fábrica Qué hace
historial_importar false Importar los mensajes del historial. Apagado, solo se procesan los metadatos.
historial_dias 90 (1 a 3650) El corte: nada más viejo se importa.
historial_mensajes 5000 (1 a 100 000) La cuota de mensajes de cada trabajo.
historial_medios false Descargar la multimedia de lo importado. Apagado, cada medio queda con sus metadatos y estado: no_descargado.

GET /v1/instances/{id}/history devuelve la política vigente (politica, con propia: true si la instancia cambió algo), los trabajos más nuevos primero (con next_cursor y has_more) y cuántos lotes llegaron.

Ventana de terminal
curl -X PATCH https://motor.ejemplo.com/v1/instances/INSTANCIA_ID \
-H "Authorization: Bearer TU_API_KEY" \
-H "Content-Type: application/json" \
-d '{"config": {"historial_importar": true, "historial_dias": 90, "historial_mensajes": 5000}}'
curl https://motor.ejemplo.com/v1/instances/INSTANCIA_ID/history \
-H "Authorization: Bearer TU_API_KEY"
GET …/history · 200 OK (resumido)
{
"politica": { "importar": true, "dias": 90, "max_mensajes": 5000, "medios": false, "propia": true },
"trabajos": [
{
"id": "5c2e7a10-0000-4000-8000-0000000000d1",
"instance_id": "8a1d2c3e-0000-4000-8000-0000000000a1",
"tipo": "inicial",
"estado": "completado",
"motivo": "cuota",
"progreso": 100,
"mensajes_vistos": 10138,
"importados": 5000,
"duplicados": 323,
"omitidos_fecha": 123,
"omitidos_cuota": 3426,
"no_soportados": 1266,
"aviso": "WhatsApp no garantiza la historia completa: es lo que el teléfono tenía y envió."
}
],
"has_more": false,
"lotes": { "recibidos": 6, "sin_contenido": 3, "pendientes": 0, "importados": 3, "omitidos": 0, "ultimo_en": "2026-10-16T22:09:40Z" },
"aviso": "WhatsApp no garantiza la historia completa: es lo que el teléfono tenía y envió."
}

Un trabajo de historial junta los lotes que llegan para un fin, con sus contadores. Hay tres:

tipo Lo crea Qué hace
inicial El primer lote del historial del vínculo, con la importación encendida Importa lo que el teléfono manda al vincular. Uno por vínculo.
pedido POST …/history/requests (comando history.request) Pide al teléfono los mensajes anteriores de un chat.
importacion POST …/history/imports (comando history.import) Pide hacia atrás, chat por chat, varios chats.

El id de un trabajo es el mismo que el command_id del comando que lo creó: GET …/history/jobs/{command_id}.

estado Qué significa
en_curso Importando o por pedir el siguiente tramo.
esperando Un pedido salió al teléfono y espera su respuesta (pedido_vence_en).
en_pausa Contrapresión: hay demasiadas entregas de webhooks pendientes en la instancia. Sigue sola.
completado Terminó por un motivo normal (ver abajo).
cancelado Lo cancelaste.
fallido No pudo terminar (por ejemplo, el teléfono no contestó).

Los contadores: mensajes_vistos (todo lo que trajeron los lotes), importados (nuevos para el motor), duplicados (ya estaban, en vivo o de otro lote), omitidos_fecha (anteriores al corte), omitidos_cuota (fuera de la cuota), no_soportados (avisos del sistema o del protocolo: se cuentan, no se inventan), pedidos, sin_respuesta, progreso (el 0-100 que informó WhatsApp en el último lote) y mas_viejo_en/mas_nuevo_en.

motivo dice por qué está en pausa o por qué terminó:

motivo Qué significa
contrapresion En pausa por las entregas pendientes.
cuota Se llegó a la cuota del trabajo.
corte Se llegó a la fecha de corte.
sin_mas_lotes El teléfono dejó de mandar lotes (el inicial).
chats_recorridos Una importación terminó con todos sus chats.
pedido_atendido El teléfono contestó el pedido.
sin_mas_historial WhatsApp marcó que no queda nada más viejo de ese chat.
fuera_del_historial El chat no vino en el historial inicial de este vínculo con mensajes que se puedan pedir (por ejemplo, empezó después de vincular).
solo_en_el_telefono El teléfono dijo que le quedan mensajes más viejos de ese chat, pero que no se pueden pedir.
sin_respuesta El teléfono no contestó a tiempo (sin conexión o, como en WhatsApp Web, sin nada más viejo).
pedido_rechazado El pedido no se pudo mandar.
no_autorizado La key que lo pidió ya no lo autoriza (revocada, sin el scope o sin la instancia).
cancelado Lo cancelaste.

POST /v1/instances/{id}/history/requests (Idempotency-Key obligatoria) pide al teléfono los cantidad mensajes (de 1 a 50; por omisión, 50) anteriores al más viejo que el motor conoce de ese chat. Responde 202 con el comando history.request, que crea el trabajo pedido.

  • Si el motor no conoce ningún mensaje del chat, 409 sin_mensajes: no hay desde dónde pedir.
  • Con la importación apagada, 409 importacion_apagada.
  • Si el teléfono no contesta en historial.espera_pedido_minutos (10 de fábrica), el trabajo termina fallido con sin_respuesta.

Para ir más atrás, repite el pedido cuando termine: cada uno parte del mensaje más viejo que ya se importó.

Ventana de terminal
curl -X POST https://motor.ejemplo.com/v1/instances/INSTANCIA_ID/history/requests \
-H "Authorization: Bearer TU_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: historial-chat-1042-tanda-1" \
-d '{"destino": {"peer_ref": "4b7d9e01-0000-4000-8000-0000000000b1"}, "cantidad": 50}'
# El trabajo: su id es el command_id del 202
curl https://motor.ejemplo.com/v1/instances/INSTANCIA_ID/history/jobs/COMMAND_ID \
-H "Authorization: Bearer TU_API_KEY"
202 Accepted
{
"command_id": "5c2e7a10-0000-4000-8000-0000000000d2",
"instance_id": "8a1d2c3e-0000-4000-8000-0000000000a1",
"idempotency_key": "historial-chat-1042-tanda-1",
"type": "history.request",
"generation": 1,
"status": "aceptado",
"created_at": "2026-10-24T16:00:00Z",
"updated_at": "2026-10-24T16:00:00Z"
}

Como WhatsApp Web, el motor solo pide los mensajes anteriores de un chat que vino en el historial inicial de este vínculo con mensajes que se pueden pedir. Con esa frontera completa (15 minutos sin lotes nuevos del historial inicial), un pedido que no tiene sentido termina al instante, sin preguntarle al teléfono:

  • fuera_del_historial: el chat no vino en el historial inicial (por ejemplo, empezó después de vincular). El teléfono no contesta esos pedidos: solo calla.
  • sin_mas_historial: el teléfono marcó en el historial que no le quedan mensajes más viejos de ese chat.
  • solo_en_el_telefono: el teléfono marcó que le quedan mensajes más viejos, pero que no se pueden pedir («usa WhatsApp en tu teléfono»).

El ajuste historial.pedido_fuera_del_historial (auto de fábrica; también pedir y no_pedir) cambia esa regla. Los demás historial.pedido_* de Ajustes son variantes del pedido para diagnóstico.

POST /v1/instances/{id}/history/imports (Idempotency-Key obligatoria) crea un trabajo importacion: pedidos sucesivos hacia atrás, chat por chat, hasta el corte, la cuota o lo que tenga el teléfono. chats es una lista de hasta 50 destinos (vacía: los 20 chats con actividad más reciente que conoce el motor); dias y max_mensajes pueden achicar la política para este trabajo (max_mensajes nunca pasa la cuota de la política).

Un chat se deja cuando no tiene mensajes, cuando llegó al corte o cuando un pedido no trajo nada más viejo; no se vuelve a pedir lo mismo. Dos pedidos seguidos sin respuesta hacen fallar la importación (sin_respuesta). Con demasiadas entregas pendientes (historial.contrapresion_entregas, 2000 de fábrica) el trabajo pasa a en_pausa y sigue solo.

Cuerpo de una importación
{
"chats": [
{"peer_ref": "4b7d9e01-0000-4000-8000-0000000000b1"},
{"jid": "GRUPO@g.us"}
],
"dias": 30,
"max_mensajes": 2000
}

Para cancelar un trabajo, POST /v1/instances/{id}/history/jobs/{job_id}/cancel: deja de importar, omite lo que faltaba y borra su contenido guardado (sync.failed con motivo: cancelado). Lo ya importado queda. Si el trabajo ya terminó, 409 trabajo_terminado.

Evento Cuándo
history.sync Llegó un lote de historial (siempre, con la importación encendida o apagada). Solo metadatos.
sync.progress Un trabajo importó un lote o entró en pausa.
sync.completed Un trabajo terminó completado.
sync.failed Un trabajo terminó fallido o cancelado.
message.received (origin: history) Cada mensaje importado, con la misma forma que en vivo.
history.sync · data
{
"tipo_sync": "INITIAL_BOOTSTRAP",
"fragmento": 1,
"progreso": 51,
"conversaciones": 400,
"mensajes": 692,
"contenido_importado": true,
"sync_job_id": "5c2e7a10-0000-4000-8000-0000000000d1"
}

tipo_sync es el del lote de WhatsApp (INITIAL_BOOTSTRAP, RECENT, FULL, ON_DEMAND, PUSH_NAME…). Con la importación apagada, contenido_importado es false y no hay sync_job_id.

sync.completed · data
{
"sync_job_id": "5c2e7a10-0000-4000-8000-0000000000d2",
"tipo": "pedido",
"estado": "completado",
"motivo": "pedido_atendido",
"lotes_recibidos": 1,
"lotes_importados": 1,
"importados": 50,
"duplicados": 0,
"omitidos_fecha": 0,
"omitidos_cuota": 0,
"no_soportados": 0,
"progreso": 0,
"mas_viejo_en": "2026-09-23T01:50:00Z",
"mas_nuevo_en": "2026-09-25T18:12:00Z",
"pedidos": 1,
"sin_respuesta": 0,
"chats": 1,
"chat_actual": 0,
"historia_completa": false,
"aviso": "WhatsApp no garantiza la historia completa: es lo que el teléfono tenía y envió."
}

sync.progress y sync.failed tienen la misma forma; en el evento, chats es la cantidad de chats del trabajo. El sobre de los tres trae el command_id del trabajo (en un pedido o una importación).

Lo importado tiene la misma identidad que lo que llega en vivo (instancia, chat estable, provider_id, dirección y operación): un mensaje que ya llegó en vivo, que viene en otro lote o que el teléfono nombra por su PN y después por su LID, se cuenta como duplicado, no se repite. Las respuestas a interactivos del historial se importan como en vivo (interactive.response con origin: history), y los estados (stories) del historial no se importan. Consulta lo importado con GET /v1/instances/{id}/messages?origen=history.

HTTP / estado Código Cuándo
409 importacion_apagada Pedir o importar con historial_importar apagado.
409 sin_mensajes El motor no conoce ningún mensaje de ese chat.
409 trabajo_terminado Cancelar un trabajo que ya terminó.
409 sin_vincular La instancia no está vinculada.
409 — La Idempotency-Key ya se usó con otros datos.
404 — El trabajo no existe en esta instancia, o la instancia no conoce ese chat.
422 — cantidad fuera de 1 a 50 (100 es 422), más de 50 chats, o dias/max_mensajes fuera de rango.
fallido sin_respuesta, pedido_rechazado, no_autorizado Ver los motivos de arriba.

Lo medido con números reales, del 16 al 23 de octubre de 2026:

  • Importación al vincular (caso A53, 16/10/2026): con la importación encendida antes de vincular (90 días, 5000 mensajes), el trabajo inicial empezó con el primer lote, mostró el progreso de WhatsApp (51 % y 100 %) y terminó por la cuota en unos 2 minutos y medio: 5000 importados de 10 138 vistos, 323 duplicados, 123 fuera del corte, 3426 fuera de la cuota y 1266 no soportados; lo más viejo, de 88 días antes. Una pausa por contrapresión se reanudó sola en ~1 minuto. Con la importación apagada (caso A12): solo history.sync con metadatos y ningún mensaje.
  • Importar varios chats (caso A55, 16/10/2026): 10 pedidos sucesivos hacia atrás, contestados en 1 a 32 s cada uno; un chat llegó al corte de 90 días y el trabajo pasó al siguiente sin repetir el pedido, hasta la cuota de 400. Tardó 3 min 46 s.
  • El historial inicial trae poco por chat: en un vínculo, el INITIAL_BOOTSTRAP trajo 555 mensajes en 400 chats (casi uno por chat), con la marca de que quedan más en el teléfono; la historia larga se pide después, chat por chat (21/10/2026).
  • Un chat hasta el límite (21/10/2026, tandas de 50): 195 tandas pedido_atendido, con 9604 mensajes vistos y 9577 importados, hasta el 28/07/2026 (unos 3 meses); mediana de 3 s por tanda, ~42 minutos en total, con algunas demoras de 79 a 476 s. Ninguna tanda repitió ni saltó mensajes. La tanda 196 terminó sin_respuesta: con WhatsApp abierto en primer plano, las 10 siguientes contestaron en 3 a 6 s, así que las demoras venían de la pantalla apagada del teléfono, no de un límite. El teléfono seguía marcando que quedaba más: no se llegó a sin_mas_historial. La base creció 26 MB; sin historial_medios, el almacenamiento no cambió.
  • Pico de recursos al vincular con historial (caso A88): el 22/10/2026, 1846 mensajes vistos y 722 importados en 104 chats, con un pico de 133 MB de RSS y 0 esperas del pool de la base; el 23/10/2026, 31 744 vistos y 5000 importados en 60 chats (el tope; 14 687 fuera de la cuota y 3800 no soportados), con un pico de 167 MB y 0 esperas, y el trabajo inicial cerró solo completado a los ~12 minutos sin lotes. Los dos, dentro de los umbrales de Capacidad.
  • El pedido de un chat (caso A54, fallas F11 y ronda R12): WhatsApp no contesta el pedido de un chat que nació después de vincular (solo calla). Con la frontera completa, el motor termina al instante con fuera_del_historial o sin_mas_historial, sin preguntarle al teléfono (23/10/2026). Ver Historial bajo pedido.

Los no_soportados se cuentan solo en total por lote (no por tipo): en las dos importaciones del caso A88 fueron 275 y 3800.