Sincronizar historial y contactos
Al conectar un número en coexistencia, puedes traer a la Cloud API el historial de conversaciones de los últimos 6 meses y la lista de contactos del dispositivo.
Tienes 24 horas contadas desde el Embedded Signup, y un solo intento por tipo de sincronización. Si se pasa el plazo o te equivocas, el cliente debe rehacer todo el onboarding. No hay recuperación.
Antes de disparar
1. El cliente autorizó el compartir en su móvil. Durante el Embedded Signup, la aplicación pide permiso para compartir historial y contactos. Si lo rechazó, no se enviará nada — y el disparo no da error, simplemente no llega ningún webhook.
2. Los eventos están suscritos. Sin esto, los datos se envían y se descartan.
curl -X PATCH https://api.joinotify.com/webhook-endpoints/whe_01J8ZK... \
-H 'Authorization: Bearer sk_live_xxx' \
-H 'Content-Type: application/json' \
-d '{ "events": ["messages", "history", "smb_app_state_sync"] }'
3. Tu servidor aguanta el volumen. Un solo webhook puede llevar miles de mensajes.
Responde 2xx al instante y procesa en segundo plano.
Disparar
curl -X POST https://api.joinotify.com/v1/106540352242922/smb_app_data \
-H 'Authorization: Bearer sk_live_xxx' \
-H 'Content-Type: application/json' \
-d '{ "messaging_product": "whatsapp", "sync_type": "history" }'
curl -X POST https://api.joinotify.com/v1/106540352242922/smb_app_data \
-H 'Authorization: Bearer sk_live_xxx' \
-H 'Content-Type: application/json' \
-d '{ "messaging_product": "whatsapp", "sync_type": "smb_app_state_sync" }'
La respuesta solo confirma la programación. Los datos llegan después, de forma asíncrona.
Cómo llega el historial
En múltiples webhooks, divididos en fases y bloques.
webhook (1 POST)
└── value.history[]
└── threads[] ← 1 hilo = 1 conversación con 1 contacto
└── messages[] ← mensajes de esa conversación
Fases
phase | Período nominal |
|---|---|
0 | día 0 → día 1 |
1 | día 1 → día 90 |
2 | día 90 → día 180 |
En la práctica, aparecen mensajes de cualquier fecha en cualquier fase. No uses la fase
para inferir un período. Ordena todo por timestamp.
Bloques y progreso
Cada fase puede llegar en varios bloques (chunk_order), no necesariamente en orden.
{ "phase": 1, "chunk_order": 3, "progress": 60 }
progress va de 0 a 100. progress: 100 en la última fase significa historial
completo — es la señal de que puedes cerrar el procesamiento.
Deduplicación — obligatoria
El mismo thread.id aparece en varios webhooks y el mismo mensaje puede llegar repetido.
- Reúne todos los mensajes con el mismo
thread.idde todos los webhooks. - Deduplica por
wamid(message.id). - Ordena por
timestamp— Unix en segundos, 10 dígitos.
const conversaciones = new Map();
function procesarHistorial(value) {
for (const bloque of value.history ?? []) {
for (const hilo of bloque.threads ?? []) {
const actual = conversaciones.get(hilo.id) ?? new Map();
for (const msg of hilo.messages ?? []) {
actual.set(msg.id, msg); // dedup por wamid
}
conversaciones.set(hilo.id, actual);
}
}
}
function finalizar(hiloId) {
return [...conversaciones.get(hiloId).values()].sort((a, b) => Number(a.timestamp) - Number(b.timestamp));
}
Dirección del mensaje
history_context.from_me: true = enviado por la empresa, false o ausente = recibido.
Medios en el historial
Los medios no viajan dentro de los hilos. Aparecen como
type: "media_placeholder", sin contenido:
{ "id": "wamid.HBgN...", "type": "media_placeholder", "timestamp": "1785312000" }
El archivo real llega en webhooks separados (value.messages o
value.message_echoes), emparejados por el wamid.
Los archivos más antiguos no tienen contenido disponible — el marcador es todo lo que recibes. Es una limitación de Meta, no de Joinotify.
Cómo llegan los contactos
Normalmente en un único webhook, en el array state_sync. Las cuentas grandes pueden
generar varios, sin orden garantizado.
{
"field": "smb_app_state_sync",
"value": {
"state_sync": [
{
"type": "contact",
"action": "add",
"contact": { "phone_number": "5541999999999", "full_name": "María Silva" },
"metadata": { "timestamp": "1785312000000" }
}
]
}
}
action: "add"→ añadir o actualizaraction: "remove"→ eliminar
Los contactos usan milisegundos (13 dígitos); el historial usa segundos (10 dígitos). Mezclarlos genera fechas en 1970 o en el año 57000.
Después de la sincronización inicial
Los cambios posteriores en los contactos siguen llegando automáticamente por
smb_app_state_sync — sin necesidad de volver a llamar al endpoint. La regla del disparo
único vale solo para la carga inicial.
Lista de verificación
- El cliente autorizó el compartir en su móvil
-
historyysmb_app_state_syncsuscritos en el endpoint de webhook - El endpoint responde
2xxen segundos y procesa en segundo plano - Deduplicación por
wamidimplementada - Ordenación por
timestamp, no por orden de llegada -
media_placeholdertratado, con reconciliación posterior porwamid - Marcas de tiempo de contacto leídas en milisegundos
- Disparo dentro de las 24 horas