Blog

API de Facebook Messenger: Crea Experiencias Conversacionales

Aprende a crear chatbots con la API de Facebook Messenger. Guía completa sobre webhooks, plantillas de mensajes, respuestas rápidas y mensajes multimedia.

Por

+8

Publica en todo. Una API.

Try Free

La API de Facebook Messenger permite a los desarrolladores crear experiencias conversacionales que alcanzan a más de 1.300 millones de usuarios activos mensuales. Ya sea que estés creando un bot de atención al cliente, un asistente de comercio electrónico o una campaña de marketing interactiva, la API de la plataforma Messenger te proporciona las herramientas necesarias para involucrar a los usuarios en conversaciones significativas.

Esta guía te lleva a través de todo, desde la configuración inicial hasta funciones avanzadas como plantillas de mensajes, respuestas rápidas y el protocolo de traspaso humano. Encontrarás ejemplos de TypeScript en funcionamiento a lo largo del texto, listos para adaptar a tus propios proyectos.

Introducción a la Plataforma Messenger

La Plataforma Messenger es el marco de Meta para crear bots e integraciones que se comunican con los usuarios a través de Facebook Messenger. A diferencia de las interfaces web tradicionales, la API de chat de Facebook genera una experiencia conversacional donde los usuarios interactúan mediante mensajes, botones y contenido multimedia enriquecido.

Las principales capacidades de la API del bot de mensajería incluyen:

FeatureDescriptionCaso de uso
Mensajes de textoEnvío y recepción de mensajes básicosConsultas de clientes, notificaciones
Plantillas de MensajesDiseños estructurados con imágenes y botonesCatálogos de productos, recibos
Respuestas RápidasBotones de respuesta sugeridosConversaciones guiadas, encuestas
Menú PersistenteOpciones de navegación siempre disponiblesNavegación del bot, acciones comunes
Mensajes de MediosImágenes, vídeos, audio y archivosImágenes del producto, tutoriales
Protocolo de TransferenciaTransferencia entre bots y agentes humanosEscalación de soporte compleja

La plataforma funciona con una arquitectura basada en webhooks. Tu servidor recibe mensajes entrantes a través de webhooks, los procesa y responde utilizando la API de Envío. Este modelo asíncrono te permite crear experiencias interactivas sin necesidad de mantener conexiones abiertas.

Configurando tu aplicación de Meta

Antes de poder utilizar la API de Facebook Messenger, necesitas crear y configurar una Aplicación de Meta. Este proceso establece la identidad de tu aplicación y otorga acceso a la Plataforma Messenger.

Paso 1: Crea una aplicación de Meta

Navega a la Meta para Desarrolladores portal y crea una nueva aplicación. Selecciona "Negocios" como tipo de aplicación, que proporciona acceso a las funciones de Messenger.

```typescript
// Variables de entorno que necesitarás después de la configuración
interface MessengerConfig {
  FACEBOOK_APP_ID: string;
  FACEBOOK_APP_SECRET: string;
  FACEBOOK_PAGE_ID: string;
  FACEBOOK_PAGE_ACCESS_TOKEN: string;
  MESSENGER_VERIFY_TOKEN: string;
}

// Valida tu configuración al iniciar
function validateConfig(): MessengerConfig {
  const required = [
    'FACEBOOK_APP_ID',
    'FACEBOOK_APP_SECRET', 
    'FACEBOOK_PAGE_ID',
    'FACEBOOK_PAGE_ACCESS_TOKEN',
    'MESSENGER_VERIFY_TOKEN'
  ];
  
  const missing = required.filter(key => !process.env[key]);
  
  if (missing.length > 0) {
    throw new Error(`Faltan las variables de entorno requeridas: ${missing.join(', ')}`);
  }
  
  return {
    FACEBOOK_APP_ID: process.env.FACEBOOK_APP_ID!,
    FACEBOOK_APP_SECRET: process.env.FACEBOOK_APP_SECRET!,
    FACEBOOK_PAGE_ID: process.env.FACEBOOK_PAGE_ID!,
    FACEBOOK_PAGE_ACCESS_TOKEN: process.env.FACEBOOK_PAGE_ACCESS_TOKEN!,
    MESSENGER_VERIFY_TOKEN: process.env.MESSENGER_VERIFY_TOKEN!
  };
}
```

Paso 2: Añadir el Producto de Messenger

En el panel de tu aplicación, haz clic en "Añadir producto" y selecciona Messenger. Esto activa las funciones de la plataforma Messenger para tu aplicación.

Paso 3: Conectar una Página de Facebook

Tu bot necesita una Página de Facebook para enviar y recibir mensajes. En la configuración de Messenger, haz clic en "Añadir o quitar páginas" y selecciona la página que deseas conectar. Genera un Token de Acceso de Página, que utilizarás para autenticar las solicitudes de la API.

Nota: Los tokens de acceso a la página pueden ser de corta o larga duración. Para aplicaciones en producción, intercambia tu token por una versión de larga duración que dure aproximadamente 60 días.

interface TokenExchangeResponse {
  access_token: string;
  token_type: string;
  expires_in?: number;
}

async function exchangeForLongLivedToken(
  shortLivedToken: string,
  appId: string,
  appSecret: string
): Promise {
  const baseUrl = 'https://graph.facebook.com/v18.0';
  
  const params = new URLSearchParams({
    grant_type: 'fb_exchange_token',
    client_id: appId,
    client_secret: appSecret,
    fb_exchange_token: shortLivedToken,
  });

  const response = await fetch(`${baseUrl}/oauth/access_token?${params.toString()}`);
  
  if (!response.ok) {
    const error = await response.text();
    throw new Error(`Intercambio de token fallido: ${error}`);
  }

  const data = await response.json();
  
  if (!data.access_token) {
    throw new Error('No se recibió ningún token de acceso en el intercambio de tokens');
  }

  const expiresInDays = data.expires_in 
    ? Math.floor(data.expires_in / 86400) 
    : 'desconocido';
    
  console.log(`Token intercambiado con éxito (expira en ${expiresInDays} días)`);

  return {
    access_token: data.access_token,
    token_type: data.token_type || 'bearer',
    expires_in: data.expires_in,
  };
}

Paso 4: Configura los permisos de la aplicación

Solicita los permisos necesarios para tu bot. Como mínimo, necesitarás:

  • páginas_mensajeríaEnviar y recibir mensajes
  • pages_gestionar_metadatos: Suscribirse a webhooks
  • páginas_leer_interacciónAccede a los datos de conversación

Configuración y verificación de Webhooks

Los webhooks son la columna vertebral de la API de la plataforma de mensajería. Permiten a Facebook notificar a tu servidor cuando ocurren eventos, como mensajes entrantes o entregas de mensajes.

Configuración de tu punto final de Webhook

Crea un endpoint que gestione tanto las solicitudes GET (para verificación) como las solicitudes POST (para recibir eventos).

import express, { Request, Response } from 'express';
import crypto from 'crypto';

const app = express();

// Analiza el cuerpo en bruto para la verificación de la firma
app.use(express.json({
  verify: (req: any, res, buf) => {
    req.rawBody = buf;
  }
}));

// Punto de verificación del webhook
app.get('/webhook', (req: Request, res: Response) => {
  const mode = req.query['hub.mode'];
  const token = req.query['hub.verify_token'];
  const challenge = req.query['hub.challenge'];
  
  const verifyToken = process.env.MESSENGER_VERIFY_TOKEN;
  
  if (mode === 'subscribe' && token === verifyToken) {
    console.log('Webhook verificado correctamente');
    res.status(200).send(challenge);
  } else {
    console.error('La verificación del webhook ha fallado');
    res.sendStatus(403);
  }
});

// Receptor de eventos del webhook
app.post('/webhook', (req: Request, res: Response) => {
  // Verifica la firma de la solicitud
  const signature = req.headers['x-hub-signature-256'] as string;
  
  if (!verifySignature(req, signature)) {
    console.error('Firma no válida');
    return res.sendStatus(401);
  }
  
  const body = req.body;
  
  if (body.object !== 'page') {
    return res.sendStatus(404);
  }
  
  // Procesa cada entrada
  body.entry?.forEach((entry: any) => {
    entry.messaging?.forEach((event: any) => {
      handleMessagingEvent(event);
    });
  });
  
  // Siempre responde con 200 OK rápidamente
  res.sendStatus(200);
});

function verifySignature(req: any, signature: string): boolean {
  if (!signature) return false;
  
  const appSecret = process.env.FACEBOOK_APP_SECRET!;
  const expectedSignature = 'sha256=' + crypto
    .createHmac('sha256', appSecret)
    .update(req.rawBody)
    .digest('hex');
    
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  );
}

async function handleMessagingEvent(event: any): Promise {
  const senderId = event.sender.id;
  
  if (event.message) {
    await handle

Suscribirse a Eventos de Webhook

Después de configurar tu endpoint, suscríbete a los eventos que deseas recibir. En el Panel de Control de la Aplicación de Meta, configura tu URL de webhook y selecciona los campos de suscripción relevantes:

  • messages: Mensajes entrantes
  • mensajes_postback: Clics en botones y selecciones de menú
  • opciones_de_mensajería: Consentimiento del usuario
  • entregas_mensajesConfirmaciones de entrega
  • lecturas_de_mensajes: Confirmaciones de lectura

Recibiendo Mensajes

Cuando los usuarios envían mensajes a tu bot, la API de chat de Facebook los entrega a tu webhook. Los mensajes pueden contener texto, archivos adjuntos o ambos.

interface MessengerMessage {
  mid: string;
  text?: string;
  attachments?: Array<{
    type: 'imagen' | 'vídeo' | 'audio' | 'archivo' | 'ubicación' | 'fallback';
    payload: {
      url?: string;
      coordinates?: {
        lat: number;
        long: number;
      };
    };
  }>;
  quick_reply?: {
    payload: string;
  };
  reply_to?: {
    mid: string;
  };
}

async function handleMessage(
  senderId: string, 
  message: MessengerMessage
): Promise {
  console.log(`Mensaje recibido de ${senderId}:`, message);
  
  // Manejar respuestas rápidas
  if (message.quick_reply) {
    await handleQuickReply(senderId, message.quick_reply.payload);
    return;
  }
  
  // Manejar archivos adjuntos
  if (message.attachments && message.attachments.length > 0) {
    for (const attachment of message.attachments) {
      await handleAttachment(senderId, attachment);
    }
    return;
  }
  
  // Manejar mensajes de texto
  if (message.text) {
    await processTextMessage(senderId, message.text);
  }
}

async function handleAttachment(
  senderId: string, 
  attachment: MessengerMessage['attachments'][0]
): Promise {
  switch (attachment.type) {
    case 'imagen':
      await sendTextMessage(senderId, `¡Gracias por la imagen! He recibido: ${attachment.payload.url}`);
      break;
    case 'ubicación':
      const { lat, long } = attachment.payload.coordinates!;
      await sendTextMessage(senderId, `He recibido tu ubicación: ${lat}, ${long}`);
      break;
    default:
      await sendTextMessage(senderId, `He recibido tu ${attachment.type}`);
  }
}

async function processTextMessage(
  senderId: string, 
  text: string
): Promise {
  const lowerText = text.toLowerCase();
  
  if (lowerText.includes('ayuda')) {
    await sendHelpMessage(senderId);
  } else if (lowerText.includes('productos')) {
    await sendProductCatalog(senderId);
  } else {
    await sendTextMessage(senderId, `Dijiste: "${text}". ¿Cómo puedo ayudarte hoy?`);
  }
}

Envío de mensajes de texto

La API de Envío es tu herramienta principal para responder a los usuarios a través de la API del bot de mensajería. Los mensajes de texto son la forma más sencilla de respuesta.

const GRAPH_API_URL = 'https://graph.facebook.com/v18.0';

interface SendMessageResponse {
  recipient_id: string;
  message_id: string;
}

interface SendMessageError {
  message: string;
  type: string;
  code: number;
  error_subcode?: number;
  fbtrace_id: string;
}

async function sendTextMessage(
  recipientId: string, 
  text: string
): Promise {
  const pageAccessToken = process.env.FACEBOOK_PAGE_ACCESS_TOKEN!;
  
  const response = await fetch(
    `${GRAPH_API_URL}/me/messages?access_token=${pageAccessToken}`,
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        recipient: { id: recipientId },
        message: { text },
        messaging_type: 'RESPONSE'
      }),
    }
  );
  
  const data = await response.json();
  
  if (!response.ok) {
    const error = data.error as SendMessageError;
    throw new Error(`Error al enviar: ${error.message} (código: ${error.code})`);
  }
  
  return data as SendMessageResponse;
}

// Enviar indicador de escritura para mejorar la experiencia de usuario
async function sendTypingIndicator(
  recipientId: string, 
  isTyping: boolean
): Promise {
  const pageAccessToken = process.env.FACEBOOK_PAGE_ACCESS_TOKEN!;
  
  await fetch(
    `${GRAPH_API_URL}/me/messages?access_token=${pageAccessToken}`,
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        recipient: { id: recipientId },
        sender_action: isTyping ? 'typing_on' : 'typing_off'
      }),
    }
  );
}

// Función auxiliar para enviar mensajes con indicador de escritura
async function sendMessageWithTyping(
  recipientId: string,
  text: string,
  typingDuration: number = 1000
): Promise {
  await sendTypingIndicator(recipientId, true);
  await new Promise(resolve => setTimeout(resolve, typingDuration));
  return sendTextMessage(recipientId, text);
}

Plantillas de Mensajes (Genéricas, Botón, Recibo)

Las plantillas de mensajes crean experiencias ricas e interactivas que van más allá del texto simple. La API de Facebook Messenger admite varios tipos de plantillas, cada una diseñada para casos de uso específicos.

Plantilla Genérica

La Plantilla Genérica muestra un carrusel de elementos, ideal para listados de productos o feeds de contenido.

interface GenericTemplateElement {
  title: string;
  subtitle?: string;
  image_url?: string;
  default_action?: {
    type: 'web_url';
    url: string;
    webview_height_ratio?: 'compact' | 'tall' | 'full';
  };
  buttons?: Array;
}

type TemplateButton = 
  | { type: 'web_url'; url: string; title: string }
  | { type: 'postback'; title: string; payload: string }
  | { type: 'phone_number'; title: string; payload: string }
  | { type: 'account_link'; url: string }
  | { type: 'account_unlink' };

async function sendGenericTemplate(
  recipientId: string,
  elements: GenericTemplateElement[]
): Promise {
  const pageAccessToken = process.env.FACEBOOK_PAGE_ACCESS_TOKEN!;
  
  // Limitado a 10 elementos por carrusel
  const limitedElements = elements.slice(0, 10);
  
  const response = await fetch(
    `${GRAPH_API_URL}/me/messages?access_token=${pageAccessToken}`,
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        recipient: { id: recipientId },
        message: {
          attachment: {
            type: 'template',
            payload: {
              template_type: 'generic',
              elements: limitedElements
            }
          }
        },
        messaging_type: 'RESPONSE'
      }),
    }
  );
  
  if (!response.ok) {
    const error = await response.json();
    throw new Error(`Error al enviar la plantilla: ${error.error?.message}`);
  }
  
  return response.json();
}

// Ejemplo: Catálogo de productos
async function sendProductCatalog(recipientId: string): Promise {
  const products: GenericTemplateElement[] = [
    {
      title: 'Auriculares Inalámbricos',
      subtitle: '$149.99 - Calidad de sonido premium',
      image_url: 'https://example.com/headphones.jpg',
      default_action: {
        type: 'web_url',
        url: 'https://example.com/products/headphones'
      },
      buttons: [
        { type: 'postback', title: 'Comprar Ahora', payload: 'BUY_HEADPHONES' },
        { type:

Plantilla de Botón

La Plantilla de Botón presenta un mensaje de texto con hasta tres botones, ideal para opciones sencillas.

async function sendButtonTemplate(
  recipientId: string,
  text: string,
  buttons: TemplateButton[]
): Promise<SendMessageResponse> {
  const pageAccessToken = process.env.FACEBOOK_PAGE_ACCESS_TOKEN!;
  
  // Limitar a 3 botones
  const limitedButtons = buttons.slice(0, 3);
  
  const response = await fetch(
    `${GRAPH_API_URL}/me/messages?access_token=${pageAccessToken}`,
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        recipient: { id: recipientId },
        message: {
          attachment: {
            type: 'template',
            payload: {
              template_type: 'button',
              text,
              buttons: limitedButtons
            }
          }
        },
        messaging_type: 'RESPONSE'
      }),
    }
  );
  
  if (!response.ok) {
    const error = await response.json();
    throw new Error(`Error en la plantilla de botones: ${error.error?.message}`);
  }
  
  return response.json();
}

// Ejemplo: Menú de ayuda
async function sendHelpMessage(recipientId: string): Promise<void> {
  await sendButtonTemplate(
    recipientId,
    '¿Cómo puedo ayudarte hoy?',
    [
      { type: 'postback', title: 'Explorar Productos', payload: 'BROWSE_PRODUCTS' },
      { type: 'postback', title: 'Rastrear Pedido', payload: 'TRACK_ORDER' },
      { type: 'postback', title: 'Contactar Soporte', payload: 'CONTACT_SUPPORT' }
    ]
  );
}

Plantilla de Recibo

La plantilla de recibo muestra las confirmaciones de pedido con detalles desglosados.

interface ReceiptElement {
  title: string;
  subtitle?: string;
  quantity?: number;
  price: number;
  currency?: string;
  image_url?: string;
}

interface ReceiptSummary {
  subtotal?: number;
  shipping_cost?: number;
  total_tax?: number;
  total_cost: number;
}

interface ReceiptAddress {
  street_1: string;
  street_2?: string;
  city: string;
  postal_code: string;
  state: string;
  country: string;
}

async function sendReceiptTemplate(
  recipientId: string,
  receipt: {
    recipientName: string;
    orderNumber: string;
    currency: string;
    paymentMethod: string;
    orderUrl?: string;
    timestamp?: string;
    address?: ReceiptAddress;
    elements: ReceiptElement[];
    summary: ReceiptSummary;
  }
): Promise<SendMessageResponse> {
  const pageAccessToken = process.env.FACEBOOK_PAGE_ACCESS_TOKEN!;
  
  const response = await fetch(
    `${GRAPH_API_URL}/me/messages?access_token=${pageAccessToken}`,
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        recipient: { id: recipientId },
        message: {
          attachment: {
            type: 'template',
            payload: {
              template_type: 'receipt',
              recipient_name: receipt.recipientName,
              order_number: receipt.orderNumber,
              currency: receipt.currency,
              payment_method: receipt.paymentMethod,
              order_url: receipt.orderUrl,
              timestamp: receipt.timestamp,
              address: receipt.address,
              elements: receipt.elements,
              summary: receipt.summary
            }
          }
        },
        messaging_type: 'RESPONSE'
      }),
    }
  );
  
  if (!response.ok) {
    const error = await response.json();
    throw new Error(`Error al enviar la plantilla de recibo: ${error.error?.message}`);
  }
  
  return response.json();
}

// Ejemplo: Confirmación de pedido
async function sendOrderConfirmation(
  recipientId: string,
  orderId: string
): Promise<void> {
  await sendReceiptTemplate(recipientId, {
    recipientName: 'Juan Pérez',

Build faster with Late

One API call to post everywhere. No OAuth headaches. No platform-specific code.

Free tier • No credit card • 99.97% uptime

Respuestas Rápidas y Menú Persistente

Las Respuestas Rápidas y el Menú Persistente guían a los usuarios a través de las conversaciones al presentar opciones claras.

Respuestas Rápidas

Las Respuestas Rápidas aparecen como botones sobre el teclado, ofreciendo respuestas sugeridas que desaparecen tras ser seleccionadas.

interface QuickReply {
  content_type: 'text' | 'user_phone_number' | 'user_email';
  title?: string;
  payload?: string;
  image_url?: string;
}

async function sendQuickReplies(
  recipientId: string,
  text: string,
  quickReplies: QuickReply[]
): Promise {
  const pageAccessToken = process.env.FACEBOOK_PAGE_ACCESS_TOKEN!;
  
  // Limitar a 13 respuestas rápidas
  const limitedReplies = quickReplies.slice(0, 13);
  
  const response = await fetch(
    `${GRAPH_API_URL}/me/messages?access_token=${pageAccessToken}`,
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        recipient: { id: recipientId },
        message: {
          text,
          quick_replies: limitedReplies
        },
        messaging_type: 'RESPONSE'
      }),
    }
  );
  
  if (!response.ok) {
    const error = await response.json();
    throw new Error(`Error al enviar las respuestas rápidas: ${error.error?.message}`);
  }
  
  return response.json();
}

// Ejemplo: Encuesta de satisfacción
async function sendSatisfactionSurvey(recipientId: string): Promise {
  await sendQuickReplies(
    recipientId,
    '¿Cómo calificarías tu experiencia hoy?',
    [
      { content_type: 'text', title: '😄 Excelente', payload: 'RATING_5' },
      { content_type: 'text', title: '🙂 Buena', payload: 'RATING_4' },
      { content_type: 'text', title: '😐 Aceptable', payload: 'RATING_3' },
      { content_type: 'text', title: '😕 Pobre', payload: 'RATING_2' },
      { content_type: 'text', title: '😞 Mala', payload: 'RATING_1' }
    ]
  );
}

async function handleQuickReply(
  senderId: string, 
  payload: string
): Promise {
  if (payload.startsWith('RATING_')) {
    const rating = parseInt(payload.split('_')[1]);
    await sendTextMessage(
      senderId, 
      `¡Gracias por tu opinión! Nos calificaste con un ${rating}/5.`
    );

Menú Persistente

El Menú Persistente ofrece una navegación siempre disponible a través del icono de hamburguesa.

interface MenuItem {
  type: 'postback' | 'web_url' | 'nested';
  title: string;
  payload?: string;
  url?: string;
  webview_height_ratio?: 'compact' | 'tall' | 'full';
  call_to_actions?: MenuItem[];
}

async function setPersistentMenu(
  menuItems: MenuItem[],
  locale: string = 'default'
): Promise {
  const pageAccessToken = process.env.FACEBOOK_PAGE_ACCESS_TOKEN!;
  
  const response = await fetch(
    `${GRAPH_API_URL}/me/messenger_profile?access_token=${pageAccessToken}`,
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        persistent_menu: [
          {
            locale,
            composer_input_disabled: false,
            call_to_actions: menuItems
          }
        ]
      }),
    }
  );
  
  if (!response.ok) {
    const error = await response.json();
    throw new Error(`Error al configurar el menú persistente: ${error.error?.message}`);
  }
  
  console.log('Menú persistente configurado correctamente');
}

// Ejemplo: Menú de bot para comercio electrónico
async function setupBotMenu(): Promise {
  await setPersistentMenu([
    {
      type: 'postback',
      title: '🛍️ Comprar Ahora',
      payload: 'SHOP_NOW'
    },
    {
      type: 'nested',
      title: '📦 Mis Pedidos',
      call_to_actions: [
        { type: 'postback', title: 'Rastrear Pedido', payload: 'TRACK_ORDER' },
        { type: 'postback', title: 'Historial de Pedidos', payload: 'ORDER_HISTORY' },
        { type: 'postback', title: 'Devoluciones', payload: 'RETURNS' }
      ]
    },
    {
      type: 'postback',
      title: '💬 Contactar Soporte',
      payload: 'CONTACT_SUPPORT'
    }
  ]);
}

Mensajes Multimedia (Imágenes, Vídeos, Archivos)

La API de la plataforma de mensajería admite archivos multimedia enriquecidos, lo que te permite enviar imágenes, vídeos, archivos de audio y documentos.

type AttachmentType = 'imagen' | 'video' | 'audio' | 'archivo';

interface AttachmentPayload {
  url?: string;
  is_reusable?: boolean;
  attachment_id?: string;
}

async function sendMediaAttachment(
  recipientId: string,
  type: AttachmentType,
  payload: AttachmentPayload
): Promise {
  const pageAccessToken = process.env.FACEBOOK_PAGE_ACCESS_TOKEN!;
  
  const response = await fetch(
    `${GRAPH_API_URL}/me/messages?access_token=${pageAccessToken}`,
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        recipient: { id: recipientId },
        message: {
          attachment: {
            type,
            payload
          }
        },
        messaging_type: 'RESPONSE'
      }),
    }
  );
  
  if (!response.ok) {
    const error = await response.json();
    throw new Error(`Error al enviar el medio: ${error.error?.message}`);
  }
  
  return response.json();
}

// Enviar imagen por URL
async function sendImage(
  recipientId: string, 
  imageUrl: string,
  reusable: boolean = true
): Promise {
  return sendMediaAttachment(recipientId, 'imagen', {
    url: imageUrl,
    is_reusable: reusable
  });
}

// Enviar video por URL
async function sendVideo(
  recipientId: string, 
  videoUrl: string
): Promise {
  return sendMediaAttachment(recipientId, 'video', {
    url: videoUrl,
    is_reusable: true
  });
}

// Enviar archivo/documento
async function sendFile(
  recipientId: string, 
  fileUrl: string
): Promise {
  return sendMediaAttachment(recipientId, 'archivo', {
    url: fileUrl,
    is_reusable: true
  });
}

// Subir archivo para reutilizar
async function uploadAttachment(
  type: AttachmentType,
  url: string
): Promise {
  const pageAccessToken = process.env.FACEBOOK_PAGE_ACCESS_TOKEN!;
  
  const response = await fetch(
    `${GRAPH_API_URL}/me/message_attachments?access_token=${pageAccessToken}`,
    {
      method: 'POST',
      headers: {
        'Content-Type

Nota: Los archivos multimedia deben ser accesibles a través de HTTPS. Facebook descarga el archivo desde tu URL, así que asegúrate de que tu servidor pueda manejar el tráfico. Para los medios que se utilizan con frecuencia, súbelos una vez y reutiliza el ID del archivo adjunto.

Protocolo de Transferencia Humana

El Protocolo de Transferencia permite transiciones fluidas entre bots automatizados y agentes humanos. Esto es esencial para escenarios de soporte complejos que requieren intervención humana.

type ThreadOwner = 'primary' | 'secondary';

interface HandoverMetadata {
  reason?: string;
  conversationHistory?: string;
  customData?: Record;
}

// Pasar el control del hilo a otra aplicación (por ejemplo, bandeja de entrada de un agente humano)
async function passThreadControl(
  recipientId: string,
  targetAppId: string,
  metadata?: HandoverMetadata
): Promise {
  const pageAccessToken = process.env.FACEBOOK_PAGE_ACCESS_TOKEN!;
  
  const response = await fetch(
    `${GRAPH_API_URL}/me/pass_thread_control?access_token=${pageAccessToken}`,
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        recipient: { id: recipientId },
        target_app_id: targetAppId,
        metadata: metadata ? JSON.stringify(metadata) : undefined
      }),
    }
  );
  
  if (!response.ok) {
    const error = await response.json();
    throw new Error(`Error al pasar el control del hilo: ${error.error?.message}`);
  }
  
  console.log(`Control del hilo pasado a la aplicación ${targetAppId}`);
}

// Recuperar el control del hilo del receptor secundario
async function takeThreadControl(
  recipientId: string,
  metadata?: HandoverMetadata
): Promise {
  const pageAccessToken = process.env.FACEBOOK_PAGE_ACCESS_TOKEN!;
  
  const response = await fetch(
    `${GRAPH_API_URL}/me/take_thread_control?access_token=${pageAccessToken}`,
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        recipient: { id: recipientId },
        metadata: metadata ? JSON.stringify(metadata) : undefined
      }),
    }
  );
  
  if (!response.ok) {
    const error = await response.json();
    throw new Error(`Error al recuperar el control del hilo: ${error.error?.message}`);
  }
  
  console.log('Control del hilo recuperado');
}

// Solicitar el control del hilo al receptor primario
async function requestThreadControl(
  recipientId: string,
  metadata?: HandoverMetadata
): Promise {
  const pageAccessToken = process.env.FACEBOOK_PAGE_ACCESS_TOKEN!;
  
  const response = await fetch(
    `${GRAPH_API_URL

Etiquetas de Mensajes para Seguimientos

Fuera de la ventana de mensajería de 24 horas, necesitas Etiquetas de Mensaje para enviar mensajes de seguimiento. Estas etiquetas indican el propósito de tu mensaje y deben usarse de manera adecuada.

type MessageTag = 
  | 'CONFIRMED_EVENT_UPDATE'
  | 'POST_PURCHASE_UPDATE'
  | 'ACCOUNT_UPDATE'
  | 'HUMAN_AGENT';;

async function sendTaggedMessage(
  recipientId: string,
  text: string,
  tag: MessageTag
): Promise {
  const pageAccessToken = process.env.FACEBOOK_PAGE_ACCESS_TOKEN!;
  
  const response = await fetch(
    `${GRAPH_API_URL}/me/messages?access_token=${pageAccessToken}`,
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        recipient: { id: recipientId },
        message: { text },
        messaging_type: 'MESSAGE_TAG',
        tag
      }),
    }
  );
  
  if (!response.ok) {
    const error = await response.json();
    throw new Error(`El envío del mensaje etiquetado ha fallado: ${error.error?.message}`);
  }
  
  return response.json();
}

// Ejemplo: Actualización del envío del pedido
async function sendShippingUpdate(
  recipientId: string,
  orderId: string,
  trackingNumber: string
): Promise {
  await sendTaggedMessage(
    recipientId,
    `¡Buenas noticias! Tu pedido #${orderId} ha sido enviado. Puedes rastrearlo aquí: https://example.com/track/${trackingNumber}`,
    'POST_PURCHASE_UPDATE'
  );
}

// Ejemplo: Alerta de seguridad de la cuenta
async function sendSecurityAlert(
  recipientId: string,
  alertType: string
): Promise {
  await sendTaggedMessage(
    recipientId,
    `Alerta de seguridad: ${alertType}. Si no fuiste tú, por favor asegura tu cuenta de inmediato.`,
    'ACCOUNT_UPDATE'
  );
}

// Ejemplo: Seguimiento por parte de un agente humano (dentro de los 7 días posteriores al mensaje del usuario)
async function sendHumanAgentFollowup(
  recipientId: string,
  message: string
): Promise {
  await sendTaggedMessage(
    recipientId,
    message,
    'HUMAN_AGENT'
  );
}

Nota: Las etiquetas de mensaje tienen políticas de uso estrictas. Su mal uso puede resultar en la restricción o prohibición de tu bot. La etiqueta HUMAN_AGENT solo está disponible dentro de los 7 días posteriores al último mensaje del usuario y requiere la intervención de un humano en la respuesta.

Límites de tasa y mejores prácticas

La API de Facebook Messenger impone límites de tasa para garantizar la estabilidad de la plataforma. Comprender estos límites te ayuda a crear bots fiables.

Descripción general del límite de tasa

Tipo de LímiteThresholdWindow
Llamadas por página200 llamadasPor hora y por usuario
Solicitudes por lotes50 solicitudesPor lote
Enviar API250 mensajespor segundo por página

Manejo de Límites de Tasa

interface EstadoLimiteTasa {
  contadorSolicitudes: number;
  inicioVentana: number;
  esLimitado: boolean;
}

class LimitadorTasa {
  private estado: Map = new Map();
  private readonly maxSolicitudes: number;
  private readonly ventanaMs: number;
  
  constructor(maxSolicitudes: number = 200, ventanaMs: number = 3600000) {
    this.maxSolicitudes = maxSolicitudes;
    this.ventanaMs = ventanaMs;
  }
  
  async comprobarLimite(idPagina: string): Promise {
    const ahora = Date.now();
    let estado = this.estado.get(idPagina);
    
    if (!estado || ahora - estado.inicioVentana > this.ventanaMs) {
      estado = { contadorSolicitudes: 0, inicioVentana: ahora, esLimitado: false };
    }
    
    if (estado.contadorSolicitudes >= this.maxSolicitudes) {
      estado.esLimitado = true;
      this.estado.set(idPagina, estado);
      return false;
    }
    
    estado.contadorSolicitudes++;
    this.estado.set(idPagina, estado);
    return true;
  }
  
  obtenerTiempoRestablecimiento(idPagina: string): number {
    const estado = this.estado.get(idPagina);
    if (!estado) return 0;
    return Math.max(0, this.ventanaMs - (Date.now() - estado.inicioVentana));
  }
}

const limitadorTasa = new LimitadorTasa();

async function enviarConLimitacionTasa(
  idPagina: string,
  idDestinatario: string,
  texto: string
): Promise {
  const puedeContinuar = await limitadorTasa.comprobarLimite(idPagina);
  
  if (!puedeContinuar) {
    const tiempoRestablecimiento = limitadorTasa.obtenerTiempoRestablecimiento(idPagina);
    console.warn(`Limitado por tasa. Restablecer en ${Math.ceil(tiempoRestablecimiento / 1000)}s`);
    return null;
  }
  
  return enviarMensajeTexto(idDestinatario, texto);
}

Mejores Prácticas

  1. Responde rápido.Reconoce los mensajes en un plazo de 20 segundos para evitar errores de tiempo de espera.
  2. Utiliza indicadores de escrituraMuestra a los usuarios que tu bot está procesando su solicitud.
  3. Gestiona los errores de forma elegante: Ofrece mensajes de error útiles cuando algo salga mal.
  4. Respeta las preferencias del usuario.Cumple con las solicitudes de exclusión y las acciones de cancelación de suscripción.
  5. Almacenar en caché los IDs de los archivos adjuntosSube los medios una vez y reutiliza el ID de adjunto.
  6. Operaciones por lotesAgrupa múltiples solicitudes siempre que sea posible.
  7. Implementa lógica de reintentosGestiona fallos transitorios con retroceso exponencial.
async function sendWithRetry(
  recipientId: string,
  text: string,
  maxRetries: number = 3
): Promise<SendMessageResponse> {
  let lastError: Error | null = null;
  
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await sendTextMessage(recipientId, text);
    } catch (error) {
      lastError = error as Error;
      
      // Comprobar si el error es reintentable
      const errorMessage = lastError.message.toLowerCase();
      const isRetryable = 
        errorMessage.includes('timeout') ||
        errorMessage.includes('temporalmente no disponible') ||
        errorMessage.includes('límite de tasa');
      
      if (!isRetryable || attempt === maxRetries) {
        throw lastError;
      }
      
      // Retraso exponencial
      const delay = Math.pow(2, attempt) * 1000;
      console.log(`Intento de reintento ${attempt}/${maxRetries} en ${delay}ms`);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
  
  throw lastError;
}

Uso de Late para la integración con Messenger

Construir y mantener una integración con Messenger implica gestionar webhooks, administrar tokens, procesar diferentes tipos de mensajes y estar al tanto de los cambios en la API. Late simplifica todo este proceso con una API unificada que funciona en múltiples plataformas de mensajería.

¿Por qué utilizar Late para Messenger?

En lugar de crear controladores de webhook personalizados, gestionar tokens y formatear mensajes para cada plataforma, Late ofrece:

  • API unificadaUn único punto de acceso para enviar mensajes a Messenger, WhatsApp, Instagram y más.
  • Actualización automática de tokensTokens de larga duración gestionados automáticamente
  • Agregación de WebhooksUn endpoint de webhook para todas las plataformas
  • Abstracción de plantillasEscribe una vez, se muestra correctamente en cada plataforma.
  • Limitación de tasa integradaManejo automático de los límites de la plataforma
  • Normalización de erroresRespuestas de error consistentes en todas las plataformas

Inicio Rápido con Late

```javascript
import { LateClient } from '@anthropic/late-sdk';

const late = new LateClient({
  apiKey: process.env.LATE_API_KEY!
});

// Enviar un mensaje a Messenger (o cualquier plataforma)
async function sendMessage(
  channelId: string,
  recipientId: string,
  content: string
): Promise {
  await late.messages.send({
    channel: channelId,
    recipient: recipientId,
    content: {
      type: 'text',
      text: content
    }
  });
}

// Enviar una plantilla que funcione en todas las plataformas
async function sendProductCard(
  channelId: string,
  recipientId: string,
  product: { name: string; price: number; imageUrl: string }
): Promise {
  await late.messages.send({
    channel: channelId,
    recipient: recipientId,
    content: {
      type: 'card',
      title: product.name,
      subtitle: `$${product.price.toFixed(2)}`,
      imageUrl: product.imageUrl,
      buttons: [
        { type: 'postback', title: 'Comprar ahora', payload: 'BUY' },
        { type: 'url', title: 'Detalles', url: 'https://example.com' }
      ]
    }
  });
}
```

Late se encarga automáticamente del formato específico de cada plataforma. El mismo código funciona para Messenger, Instagram Direct, WhatsApp Business y otras plataformas compatibles.

Gestión de Mensajes Entrantes

Late agrega webhooks de todas las plataformas conectadas en un único punto de acceso:

// Manejador de webhook único para todas las plataformas
app.post('/late-webhook', async (req, res) => {
  const event = req.body;
  
  // Manejo de mensajes independiente de la plataforma
  if (event.type === 'message') {
    const { platform, channelId, senderId, content } = event;
    
    console.log(`Mensaje de ${platform}: ${content.text}`);
    
    // Responder utilizando la API unificada de Late
    await late.messages.send({
      channel: channelId,
      recipient: senderId,
      content: {
        type: 'text',
        text: `¡Gracias por tu mensaje!`
      }
    });
  }
  
  res.sendStatus(200);
});

Empezando

  1. Regístrate en getlate.dev
  2. Conecta tu Página de Facebook en el panel de control.
  3. Utiliza la API unificada de Late para enviar y recibir mensajes.

La documentación de Late incluye guías detalladas para Integración con Messenger, configuración de webhook, y plantillas de mensajes.

Crear experiencias conversacionales no debería requerir mantener diferentes bases de código para cada plataforma. Con Late, escribes la lógica de tu bot una vez y llegas a los usuarios donde prefieren comunicarse.

Una API. 13+ plataformas.

Integra redes sociales en minutos, no semanas.

Diseñada para desarrolladores. Usada por agencias. Más de 6,325 usuarios.