Chatwoot muestra “John Doe” en todos los contactos de Facebook Messenger: Causa y Solución Definitiva
Todos tus contactos de Facebook Messenger aparecen como “John Doe” en Chatwoot? No eres el único. Este es un problema que afecta a prácticamente todas las instalaciones de Chatwoot con Facebook Messenger desde que Meta cambió su Graph API. En este artículo te explico por qué pasa y cómo solucionarlo de forma definitiva en tu servidor.
El problema: todos tus contactos se llaman “John Doe”
Si usas Chatwoot con un inbox de Facebook Messenger, probablemente ya lo notaste: cada vez que un cliente te escribe por Messenger, Chatwoot crea el contacto con el nombre “John Doe” y sin foto de perfil.
Esto es lo que ves en tu panel de contactos:
- John Doe — sin avatar
- John Doe — sin avatar
- John Doe — sin avatar
- … cientos de veces
Imposible saber quién es quién. Imposible buscar un cliente por nombre. Un desastre operativo.
Por qué pasa: Meta deprecó el endpoint de perfil por PSID
Cuando un usuario te escribe por Messenger, Facebook le asigna un PSID (Page-Scoped User ID). Chatwoot usa este PSID para obtener el nombre y foto del usuario haciendo esta llamada a la Graph API de Facebook:
GET /{PSID}?fields=first_name,last_name,profile_pic
Este endpoint ya no funciona para PSIDs nuevos. Meta lo deprecó silenciosamente. La respuesta que devuelve ahora es:
{
"error": {
"message": "(#100) No profile available for this user",
"type": "OAuthException",
"code": 100,
"error_subcode": 33
}
}
Cuando Chatwoot recibe este error, su código de fallback asigna el nombre por defecto: “John” como first_name y “Doe” como last_name. Así se ve en el código fuente original de Chatwoot (app/builders/messages/facebook/message_builder.rb):
def process_contact_params_result(result)
{
name: "#{result['first_name'] || 'John'} #{result['last_name'] || 'Doe'}",
account_id: @inbox.account_id,
avatar_url: result['profile_pic']
}
end
El problema no es de Chatwoot — es que Meta cambió las reglas del juego y Chatwoot todavía no actualizó su código.
La solución: Conversations API como fallback
Después de investigar la Graph API de Meta, encontré que hay un endpoint alternativo que sí devuelve el nombre del usuario a partir de su PSID:
GET /me/conversations?user_id={PSID}&fields=participants
Esta llamada devuelve la conversación entre tu página y ese usuario, incluyendo los datos del participante:
{
"data": [
{
"participants": {
"data": [
{ "name": "Tu Página de Facebook", "id": "PAGE_ID" },
{ "name": "Jose Sevi", "id": "25916950971308219" }
]
},
"id": "t_conversation_id"
}
]
}
Filtrando el participante que no es tu página, obtienes el nombre real del cliente. Con esto podemos parchear Chatwoot sin modificar su código fuente directamente.
Implementación paso a paso
Paso 1: Crear el initializer con monkey-patch
Chatwoot carga automáticamente cualquier archivo .rb en /app/config/initializers/ al arrancar. La estrategia es montar un archivo externo vía Docker que haga override del método contact_params.
Creamos el archivo /opt/docker/chatwoot/custom_initializers/facebook_profile_fix.rb:
# Fix: Facebook Messenger contact name resolution
# Facebook deprecated GET /{PSID}?fields=first_name,last_name,profile_pic
# Fallback: GET /me/conversations?user_id={PSID}&fields=participants
Rails.application.config.to_prepare do
Messages::Facebook::MessageBuilder.class_eval do
private
def contact_params
result = {}
begin
k = Koala::Facebook::API.new(@inbox.channel.page_access_token) if @inbox.facebook?
result = k.get_object(@sender_id, fields: 'first_name,last_name,profile_pic') || {}
rescue Koala::Facebook::AuthenticationError => e
Rails.logger.warn("Facebook authentication error for inbox: #{@inbox.id}")
@inbox.channel.authorization_error!
raise
rescue Koala::Facebook::ClientError => e
result = {}
if e.message.include?('2018218')
Rails.logger.warn e
else
# Fallback: Conversations API
result = fetch_name_via_conversations_api || {}
unless @outgoing_echo
ChatwootExceptionTracker.new(e, account: @inbox.account).capture_exception if result.empty?
end
end
rescue StandardError => e
result = {}
ChatwootExceptionTracker.new(e, account: @inbox.account).capture_exception
end
process_contact_params_result(result)
end
def fetch_name_via_conversations_api
k = Koala::Facebook::API.new(@inbox.channel.page_access_token)
page_id = @inbox.channel.page_id
response = k.get_object('me/conversations', user_id: @sender_id, fields: 'participants')
conversations = response.is_a?(Array) ? response : (response['data'] rescue [response])
return nil if conversations.blank?
conversation = conversations.first
participants = conversation.dig('participants', 'data') || []
# Find the participant that is NOT the page
contact = participants.find { |p| p['id'] != page_id.to_s }
return nil unless contact
full_name = contact['name'].to_s.strip
return nil if full_name.blank?
parts = full_name.split(' ', 2)
first_name = parts[0]
last_name = parts[1] || ''
Rails.logger.info("Facebook profile fix: resolved PSID #{@sender_id} -> '#{full_name}' via Conversations API")
{ 'first_name' => first_name, 'last_name' => last_name }
rescue StandardError => e
Rails.logger.warn("Facebook profile fix: Conversations API fallback failed for PSID #{@sender_id}: #{e.message}")
nil
end
end
end
Cómo funciona la lógica
- Intenta el endpoint original (
GET /{PSID}?fields=first_name,last_name,profile_pic) — por si Meta lo reactiva o funciona para algunos PSIDs legacy. - Si falla con error diferente a 2018218 (que es “echo message, no profile needed”), activa el fallback con Conversations API.
- Extrae el nombre del participante que no es la página.
- Separa en first_name y last_name haciendo split por el primer espacio.
- Loguea el resultado para monitoreo.
Paso 2: Montar el archivo en Docker
En tu docker-compose.yml, agrega el volumen en todos los servicios que procesan mensajes de Facebook (web, worker-fast, worker-general):
services:
chatwoot:
volumes:
- /opt/docker/chatwoot/storage:/app/storage
- /opt/docker/chatwoot/custom_initializers/facebook_profile_fix.rb:/app/config/initializers/facebook_profile_fix.rb
chatwoot-worker-fast:
volumes:
- /opt/docker/chatwoot/storage:/app/storage
- /opt/docker/chatwoot/custom_initializers/facebook_profile_fix.rb:/app/config/initializers/facebook_profile_fix.rb
chatwoot-worker-general:
volumes:
- /opt/docker/chatwoot/storage:/app/storage
- /opt/docker/chatwoot/custom_initializers/facebook_profile_fix.rb:/app/config/initializers/facebook_profile_fix.rb
Importante: debe estar en los tres servicios. Si solo lo pones en el web server, los workers de Sidekiq (que son los que realmente procesan los webhooks de Facebook) no tendrán el parche.
Paso 3: Reiniciar contenedores
cd /opt/docker/chatwoot
docker compose up -d --force-recreate chatwoot chatwoot-worker-fast chatwoot-worker-general
Usamos --force-recreate en lugar de restart porque necesitamos que Docker reconozca el nuevo volumen montado.
Paso 4: Verificar que cargó correctamente
docker exec chatwoot bundle exec rails runner "
method = Messages::Facebook::MessageBuilder.instance_method(:contact_params)
puts method.source_location
"
Deberías ver:
/app/config/initializers/facebook_profile_fix.rb
9
Si ves que apunta al archivo original en /app/app/builders/messages/facebook/message_builder.rb, el parche no cargó.
Migración de contactos existentes
El fix anterior solo aplica para mensajes nuevos. Los contactos “John Doe” que ya existen necesitan actualizarse con un script de migración.
Script de migración controlado
Este script procesa los contactos en lotes de 20, con pausas entre llamadas para no saturar el servidor ni la API de Facebook:
require 'koala'
BATCH_SIZE = 20
DELAY_BETWEEN_CALLS = 1.5 # segundos entre cada llamada API
DELAY_BETWEEN_BATCHES = 5 # segundos entre lotes
inbox = Inbox.find(YOUR_INBOX_ID) # Cambia por el ID de tu inbox de Facebook
channel = inbox.channel
k = Koala::Facebook::API.new(channel.page_access_token)
page_id = channel.page_id.to_s
contact_inboxes = ContactInbox.where(inbox_id: inbox.id)
.joins(:contact)
.where(contacts: { name: 'John Doe' })
.includes(:contact)
total = contact_inboxes.count
puts "=== Migration Start: #{total} John Doe contacts ==="
updated = 0
skipped = 0
errored = 0
batch_num = 0
contact_inboxes.find_in_batches(batch_size: BATCH_SIZE) do |batch|
batch_num += 1
puts "--- Batch #{batch_num} ---"
batch.each do |ci|
psid = ci.source_id
contact = ci.contact
next if contact.name != 'John Doe'
begin
response = k.get_object('me/conversations', user_id: psid, fields: 'participants')
conversations = response.is_a?(Array) ? response : (response['data'] rescue [response])
if conversations.blank?
skipped += 1
sleep(DELAY_BETWEEN_CALLS)
next
end
conv = conversations.first
participants = conv.dig('participants', 'data') || []
contact_data = participants.find { |p| p['id'] != page_id }
if contact_data.nil? || contact_data['name'].blank?
skipped += 1
sleep(DELAY_BETWEEN_CALLS)
next
end
new_name = contact_data['name'].strip
contact.update!(name: new_name)
updated += 1
puts " OK #{psid} -> '#{new_name}'"
rescue => e
errored += 1
puts " ERR #{psid} -> #{e.message}"
end
sleep(DELAY_BETWEEN_CALLS)
end
puts " Progress: #{updated} updated, #{skipped} skipped, #{errored} errors"
sleep(DELAY_BETWEEN_BATCHES)
end
puts "=== Complete: #{updated} updated, #{skipped} skipped, #{errored} errors ==="
Para ejecutarlo:
# Guardar como migrate_john_doe.rb y ejecutar:
cat migrate_john_doe.rb | docker exec -i chatwoot bundle exec rails runner -
Tip: Si tienes muchos contactos, ejecútalo con nohup en background para que no se corte si pierdes la conexión SSH:
nohup bash -c 'cat migrate_john_doe.rb | docker exec -i chatwoot bundle exec rails runner - > migrate.log 2>&1' &
tail -f migrate.log
Limitaciones que debes conocer
Sin foto de perfil
La Conversations API devuelve el nombre del usuario pero no su profile_pic. Los contactos tendrán nombre correcto pero sin avatar. Hasta que Meta no habilite otro endpoint, no hay forma de obtener la foto.
Nombre completo sin separar
La API devuelve name (nombre completo), no first_name y last_name por separado. El script hace split por el primer espacio, lo cual funciona bien en la mayoría de los casos pero no es perfecto para nombres compuestos.
Depende de conversación activa
Si el PSID no tiene un thread de conversación con tu página, la Conversations API no devolverá resultados. En la práctica esto casi nunca pasa porque si alguien te escribió por Messenger, ya existe la conversación.
Contactos con cuenta eliminada
Si un usuario eliminó su cuenta de Facebook, Meta no devolverá datos. Estos contactos seguirán como “John Doe” y no hay forma de resolverlos.
Versiones probadas
- Chatwoot: v4.11.1 (Docker)
- Graph API de Facebook: v21.0
- Ruby: 3.4.0
- Koala gem: incluida en Chatwoot
El fix debería funcionar en cualquier versión de Chatwoot que use Messages::Facebook::MessageBuilder con el método contact_params (v3.x y v4.x).
Preguntas frecuentes
Esto se va a romper cuando actualice Chatwoot?
No. El initializer usa class_eval que hace override del método en runtime. Si Chatwoot eventualmente arregla esto en su código fuente, puedes simplemente eliminar el initializer y el volumen de Docker. El monkey-patch siempre tiene prioridad sobre el código original.
Puedo usar esto en Chatwoot Cloud (hosted)?
No. Este fix requiere acceso al servidor y modificar la configuración de Docker. Solo funciona en instalaciones self-hosted.
Afecta a WhatsApp o a otros canales?
No. El fix solo se activa para inboxes de tipo Facebook Page (@inbox.facebook?). WhatsApp, Telegram, email y los demás canales no se ven afectados.
Cuánto tarda la migración de contactos existentes?
Depende de cuántos tengas. Con la configuración conservadora del script (1.5s entre llamadas, lotes de 20), procesa aproximadamente 25 contactos por minuto.
Puedo acelerar la migración?
Sí, puedes reducir DELAY_BETWEEN_CALLS a 0.5 segundos y DELAY_BETWEEN_BATCHES a 2 segundos. Pero ten cuidado con los rate limits de la Graph API de Facebook (200 llamadas por hora por usuario).
Ya hice la migración pero siguen apareciendo “John Doe” nuevos.
Verifica que el initializer esté montado en el servicio worker-fast, no solo en el web server. Los webhooks de Facebook los procesan los workers de Sidekiq.
Necesitas ayuda con tu Chatwoot?
Si tu instalación de Chatwoot tiene este problema u otros (rendimiento, configuración de WhatsApp Business API, integraciones, migración de datos), puedo ayudarte a resolverlo.
Tengo experiencia con:
- Chatwoot self-hosted en Docker (configuración, optimización, troubleshooting)
- Integración con Facebook Messenger, WhatsApp Business API, Instagram
- Automatización con n8n y webhooks
- Migración y limpieza de bases de datos PostgreSQL
- Custom initializers y monkey-patches para extender Chatwoot sin tocar el código fuente
Contacto: carlos@bucaink.com
Última actualización: Marzo 2026
Probado en Chatwoot v4.11.1 con Docker