WhatsApp Business API + CRM desde $2,999 MXN/mes — Agentes ilimitados, sin comisiones

Ver planes
Chatwoot · Integraciones

Chatwoot muestra John Doe en todos los contactos de Facebook Messenger: Causa y Solución Definitiva

marzo 9, 2026 · admin

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:

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

  1. Intenta el endpoint original (GET /{PSID}?fields=first_name,last_name,profile_pic) — por si Meta lo reactiva o funciona para algunos PSIDs legacy.
  2. Si falla con error diferente a 2018218 (que es “echo message, no profile needed”), activa el fallback con Conversations API.
  3. Extrae el nombre del participante que no es la página.
  4. Separa en first_name y last_name haciendo split por el primer espacio.
  5. 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

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:

Contacto: carlos@bucaink.com


Última actualización: Marzo 2026
Probado en Chatwoot v4.11.1 con Docker

¿Listo para vender en línea?

WhatsApp Business API + CRM desde $2,999/mes. Agentes ilimitados, chatbot IA incluido.

  Quiero mi CRM