Cuando intentas integrar WhatsApp Business Cloud API, te encuentras con un problema frustrante: usar la Cloud API desconecta tu WhatsApp Business App del móvil. Esto significa que pierdes acceso a tus conversaciones históricas y ya no puedes responder desde tu teléfono.
¿La solución? WhatsApp Business Coexistence - una funcionalidad que te permite usar ambos simultáneamente.
🎯 ¿Qué es Coexistencia?
Coexistencia permite que la misma cuenta de WhatsApp Business funcione en:
- WhatsApp Business App (móvil) - Para responder manualmente
- Cloud API (servidor) - Para automatizar con chatbots
Sin coexistencia: Elegir Cloud API = Perder acceso móvil Con coexistencia: Ambos funcionan al mismo tiempo ✅
📋 Requisitos Previos
Antes de comenzar, necesitas:
- Cuenta de WhatsApp Business con número verificado
- Acceso a Meta for Developers
- Embedded Signup implementado
- Webhook endpoint configurado
🔑 Paso 1: Activar Coexistencia Durante Onboarding
La coexistencia se activa al conectar tu cuenta. En tu flujo de Embedded Signup:
// Al recibir el código de autorización
const response = await fetch(
`https://graph.facebook.com/v21.0/oauth/access_token`,
{
method: 'POST',
body: JSON.stringify({
client_id: YOUR_APP_ID,
client_secret: YOUR_APP_SECRET,
code: authorizationCode,
}),
}
);Importante: El usuario DEBE aceptar "Compartir historial" durante el flujo de onboarding para activar coexistencia.
📡 Paso 2: Suscribir Webhooks para History Sync
Después del onboarding, solicita la sincronización del historial:
// Endpoint: POST https://graph.facebook.com/v21.0/{phone_number_id}/smb_app_data
async function initializeHistorySync(phoneNumberId: string, accessToken: string) {
// Paso 1: Sincronizar contactos
await fetch(
`https://graph.facebook.com/v21.0/${phoneNumberId}/smb_app_data`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
messaging_product: 'whatsapp',
sync_type: 'smb_app_state_sync', // Contactos
}),
}
);
// Paso 2: Sincronizar historial de mensajes
await fetch(
`https://graph.facebook.com/v21.0/${phoneNumberId}/smb_app_data`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
messaging_product: 'whatsapp',
sync_type: 'history', // Historial (hasta 6 meses)
}),
}
);
}⏱️ Timing Crítico: Debes llamar esto dentro de las 24 horas después del onboarding.
🎣 Paso 3: La Estructura REAL del History Sync Webhook
Aquí está el descubrimiento clave. Meta NO envía la estructura que su documentación sugiere.
❌ Lo que esperarías (según docs):
{
"phone_number_id": "123456789",
"messages": [
{ "from": "521...", "text": { "body": "Hola" } }
]
}✅ Lo que REALMENTE envía Meta:
{
"messaging_product": "whatsapp",
"metadata": {
"phone_number_id": "845237608662425"
},
"history": [
{
"metadata": {
"phase": 1,
"chunk_order": 5,
"progress": 100
},
"threads": [
{
"id": "5217715268513",
"messages": [
{
"from": "5217712412825",
"id": "wamid.HBg...",
"timestamp": "1760104640",
"text": { "body": "Hola" },
"type": "text",
"history_context": {
"status": "read",
"from_me": true
}
}
]
}
]
}
]
}💻 Paso 4: Procesar el History Sync Correctamente
Aquí está el código para parsear la estructura real:
// En tu webhook handler
export async function handleHistorySyncWebhook(payload: any) {
// ✅ Obtener phone_number_id del metadata
const phoneNumberId = payload.metadata?.phone_number_id;
const historyArray = payload.history || [];
if (!phoneNumberId) {
console.warn('No phone_number_id found');
return;
}
// ✅ Iterar sobre history array
for (const historyItem of historyArray) {
const metadata = historyItem.metadata || {};
const progress = metadata.progress || 0;
const phase = metadata.phase || 1;
const threads = historyItem.threads || [];
console.log(`Processing chunk: phase ${phase}, progress ${progress}%`);
// ✅ Iterar sobre threads (conversaciones)
for (const thread of threads) {
const contactPhone = thread.id; // Thread ID = número del contacto
const messages = thread.messages || [];
console.log(`Thread ${contactPhone}: ${messages.length} messages`);
// ✅ Iterar sobre mensajes del thread
for (const msg of messages) {
// Determinar dirección usando history_context
const isFromBusiness = msg.history_context?.from_me === true;
// Solo procesar mensajes de texto
if (msg.type !== 'text') continue;
// Guardar mensaje en base de datos
await saveMessage({
conversationId: await getOrCreateConversation(contactPhone),
content: msg.text?.body || '',
role: isFromBusiness ? 'ASSISTANT' : 'USER',
timestamp: new Date(parseInt(msg.timestamp) * 1000),
externalId: msg.id,
});
}
}
}
}🔍 Puntos Clave del Código
1. El Thread ID es el Contacto
const contactPhone = thread.id; // ✅ Este es el número del cliente2. Usar history_context.from_me para Dirección
const isFromBusiness = msg.history_context?.from_me === true;
// from_me: true → Mensaje enviado por tu negocio (ASSISTANT)
// from_me: false → Mensaje enviado por el cliente (USER)3. Progress Tracking
const progress = metadata.progress || 0;
if (progress === 100) {
console.log('✅ History sync completed!');
// Actualizar estado en DB
await markSyncCompleted(phoneNumberId);
}📊 Paso 5: Mostrar Estado de Sincronización
Crea un banner en tu UI para mostrar el progreso:
function SyncStatusBanner({ integrationId }: { integrationId: string }) {
const [syncStatus, setSyncStatus] = useState<'syncing' | 'completed' | 'failed'>('syncing');
useEffect(() => {
// Poll cada 5 segundos
const interval = setInterval(async () => {
const status = await fetch(`/api/sync/status?id=${integrationId}`);
const data = await status.json();
setSyncStatus(data.syncStatus);
if (data.syncStatus === 'completed') {
clearInterval(interval);
}
}, 5000);
return () => clearInterval(interval);
}, [integrationId]);
const config = {
syncing: { icon: '🔄', text: 'Sincronizando WhatsApp...', color: 'blue' },
completed: { icon: '✅', text: 'Sincronización completada', color: 'green' },
failed: { icon: '⚠️', text: 'Error en sincronización', color: 'red' },
};
return (
<div className={`banner banner-${config[syncStatus].color}`}>
<span>{config[syncStatus].icon}</span>
<span>{config[syncStatus].text}</span>
</div>
);
}⚠️ Errores Comunes y Soluciones
Error: "No history data received"
Causa: Usuario no aceptó "Compartir historial" durante onboarding
Solución:
// Verificar error específico de Meta
if (errorText.includes('2593109')) {
console.log('User declined history sharing - this is expected');
// No marcar como error fatal
}Error: "Timeout esperando webhooks"
Causa: History sync puede tardar varios minutos
Solución:
// Usar timeout pragmático
const shouldComplete =
progress === 100 ||
(timeSinceLastWebhook > 60 && lastProgress > 0);Error: "Phone number undefined"
Causa: Buscar phone_number_id en lugar equivocado
Solución:
// ❌ Incorrecto
const phoneNumberId = payload.phone_number_id;
// ✅ Correcto
const phoneNumberId = payload.metadata?.phone_number_id;🎓 Checklist de Implementación
Para implementar coexistencia correctamente:
- Embedded Signup configurado con scope
whatsapp_business_messaging - Usuario acepta "Compartir historial" durante onboarding
- Webhook subscrito a campo
history - Sincronización iniciada dentro de 24 horas post-onboarding
- Parser maneja estructura
history[].threads[].messages[] - Usar
history_context.from_mepara determinar dirección - Progress tracking implementado (0-100%)
- Timeout pragmático para cuentas con poco historial
🚀 Beneficios de Coexistencia
Una vez implementado correctamente:
✅ Respuestas automáticas por chatbot 24/7 ✅ Respuestas manuales desde móvil cuando sea necesario ✅ Historial completo sincronizado (hasta 6 meses) ✅ Contactos compartidos entre móvil y Cloud API ✅ Sin perder acceso a ninguna plataforma
📚 Recursos Adicionales
Conclusión
WhatsApp Business Coexistence es la clave para aprovechar lo mejor de ambos mundos: automatización potente con Cloud API y flexibilidad manual con la app móvil.
La estructura real de los webhooks (history[].threads[]) es diferente a la documentación, pero con este tutorial tienes todo lo necesario para implementarlo correctamente desde la primera vez.
¿Quieres integrar WhatsApp Business con coexistencia automáticamente?
Prueba Formmy - manejamos toda la complejidad de Meta por ti, incluyendo History Sync, webhooks y sincronización.
¿Tienes preguntas sobre la implementación? ¡Nuestro equipo está listo para ayudarte!
