WhatsApp Cloud API

Todo lo necesario para enviar y recibir mensajes a través del WABA del negocio: mensajes simples y con plantilla, multimedia, botones y listas interactivas, marcar como leído, gestionar plantillas, perfil del negocio, diagnostics del número y estadísticas.

Para conceptos comunes (autenticación, scopes, errores, idempotencia) andá a la página principal. Para recibir mensajes entrantes ver Webhooks. Para conectar el WhatsApp del negocio por primera vez ver Onboarding.

Enviar mensajes

POST /v1/messages

Plantilla simple (primer mensaje a un contacto):

POST /v1/messages
{
  "tenant_ref": "pizzeria-don-mario",
  "to": "5491155555555",
  "type": "template",
  "template": {
    "name": "hello_world",
    "language": "en_US"
  }
}

Plantilla con variables:

POST /v1/messages
{
  "tenant_ref": "pizzeria-don-mario",
  "to": "5491155555555",
  "type": "template",
  "template": {
    "name": "pedido_listo",
    "language": "es",
    "components": [
      {
        "type": "body",
        "parameters": [
          { "type": "text", "text": "Juan" },
          { "type": "text", "text": "12345" }
        ]
      }
    ]
  }
}

Texto libre (solo si la conversación tiene una sesión activa de 24h):

POST /v1/messages
{
  "tenant_ref": "pizzeria-don-mario",
  "to": "5491155555555",
  "type": "text",
  "text": {
    "body": "Hola, gracias por escribirnos. ¿En qué te ayudo?"
  }
}

Errores:

Todos estos vienen como {"ok": false, "error", "message", "meta_code"}. La respuesta de un envío exitoso es 201 (o 200 si fue un reintento con la misma Idempotency-Key) con la misma forma que GET /v1/messages/{id}.

GET /v1/messages/{id}

Consulta el estado actual de un mensaje enviado. El {id} es el id que te devolvió POST /v1/messages — no el wamid de Meta.

GET /v1/messages/019dcf...

Respuesta:

{
  "id": "019dcf...",
  "tenant_ref": "pizzeria-don-mario",
  "channel": "whatsapp",
  "type": "template",
  "status": "delivered",
  "to": "5491155555555",
  "message_id": "wamid.HBg...",
  "waba_message_id": "wamid.HBg...",
  "error_code": null,
  "error_message": null,
  "idempotency_key": "pedido-12345-confirmacion",
  "created_at": "2026-05-07T15:30:00Z",
  "updated_at": "2026-05-07T15:30:12Z"
}

status: queued → sent → delivered → read, o failed con error_code y error_message de Meta. No retrocede: un delivered que llega tarde no pisa un read. waba_message_id y message_id son lo mismo (el primero queda por compatibilidad y solo existe en WhatsApp). Si el id no es de tu cuenta, 404 message_not_found.

Imágenes, audio, video y documentos

Adjuntá multimedia por URL pública (Meta la descarga). Los formatos aceptados los define Meta — los más comunes andan sin problema.

Imagen

POST /v1/messages
{
  "tenant_ref": "pizzeria-don-mario",
  "to": "5491155555555",
  "type": "image",
  "image": {
    "link": "https://tu-cdn.com/pizza.jpg",
    "caption": "Hoy en promo: muzza con fainá."
  }
}

Video

POST /v1/messages
{
  "tenant_ref": "pizzeria-don-mario",
  "to": "5491155555555",
  "type": "video",
  "video": {
    "link": "https://tu-cdn.com/horno.mp4",
    "caption": "Recién salida del horno."
  }
}

Audio

POST /v1/messages
{
  "tenant_ref": "pizzeria-don-mario",
  "to": "5491155555555",
  "type": "audio",
  "audio": { "link": "https://tu-cdn.com/aviso.ogg" }
}

Documento

POST /v1/messages
{
  "tenant_ref": "pizzeria-don-mario",
  "to": "5491155555555",
  "type": "document",
  "document": {
    "link": "https://tu-cdn.com/factura-12345.pdf",
    "filename": "factura-12345.pdf",
    "caption": "Tu factura."
  }
}

Sticker

POST /v1/messages
{
  "tenant_ref": "pizzeria-don-mario",
  "to": "5491155555555",
  "type": "sticker",
  "sticker": { "link": "https://tu-cdn.com/gracias.webp" }
}

Stickers tienen que ser .webp y cumplir el tamaño exacto que pide Meta (512x512 px, máx 100 KB para estáticos). Si Meta los rechaza, te devolvemos meta_other con el detalle.

Botones y listas

Los mensajes interactivos permiten que el contacto te responda con un click. Las respuestas te llegan por webhook como message.received con type: "interactive": el id que pusiste acá vuelve tal cual en data.message.interactive.button_reply.id (o list_reply.id), y data.message.context.id te dice a qué mensaje tuyo están contestando. La forma exacta está en Webhooks → cuando tocan un botón.

El botón de una plantilla no es esto. Un quick reply de template llega con type: "button" y button.payload, no como interactive. Son dos formas distintas y conviene manejar las dos.

Botones de respuesta rápida (Quick Reply)

Hasta 3 botones. El id es lo que te llega cuando aprieta.

POST /v1/messages
{
  "tenant_ref": "pizzeria-don-mario",
  "to": "5491155555555",
  "type": "interactive",
  "interactive": {
    "type": "button",
    "body": { "text": "¿Confirmás tu pedido #12345?" },
    "action": {
      "buttons": [
        { "type": "reply", "reply": { "id": "ok", "title": "Sí, confirmar" } },
        { "type": "reply", "reply": { "id": "cancel", "title": "No, cancelar" } }
      ]
    }
  }
}

Lista de opciones

Hasta 10 opciones agrupadas en secciones. Útil para menús.

POST /v1/messages
{
  "tenant_ref": "pizzeria-don-mario",
  "to": "5491155555555",
  "type": "interactive",
  "interactive": {
    "type": "list",
    "body": { "text": "Elegí una opción." },
    "action": {
      "button": "Ver menú",
      "sections": [
        {
          "title": "Pizzas",
          "rows": [
            { "id": "muzza", "title": "Muzzarella", "description": "$3500" },
            { "id": "napoli", "title": "Napolitana", "description": "$4200" }
          ]
        },
        {
          "title": "Bebidas",
          "rows": [
            { "id": "coca", "title": "Coca 1.5L", "description": "$1800" }
          ]
        }
      ]
    }
  }
}

Ubicación, contactos y reacciones

Ubicación

POST /v1/messages
{
  "tenant_ref": "pizzeria-don-mario",
  "to": "5491155555555",
  "type": "location",
  "location": {
    "latitude": -34.6037,
    "longitude": -58.3816,
    "name": "Pizzería Don Mario",
    "address": "Av. Corrientes 1234, CABA"
  }
}

Contacto

POST /v1/messages
{
  "tenant_ref": "pizzeria-don-mario",
  "to": "5491155555555",
  "type": "contacts",
  "contacts": [
    {
      "name": { "formatted_name": "Mario Rossi", "first_name": "Mario" },
      "phones": [
        { "phone": "+5491155551234", "type": "CELL" }
      ]
    }
  ]
}

Reacción a un mensaje

Reaccionás con un emoji a un mensaje que te mandó el contacto. Necesitás el waba_message_id que te llegó por webhook.

POST /v1/messages
{
  "tenant_ref": "pizzeria-don-mario",
  "to": "5491155555555",
  "type": "reaction",
  "reaction": {
    "message_id": "wamid.HBg...",
    "emoji": "👍"
  }
}

Para sacar una reacción, mandá la misma estructura con "emoji": "" (string vacío).

Reglas de Meta: el mensaje tiene que tener menos de 30 días, no puede estar borrado ni ser a su vez una reacción. Si no se cumple, la reacción no se entrega y te llega un message.status failed con el código 131009. De una reacción solo vas a ver el estado sent: Meta no manda delivered ni read para reacciones.

Las reacciones del contacto también te llegan: como un message.received con type: "reaction" y reaction.message_id apuntando al mensaje reaccionado. Ver Webhooks.

Responder a un mensaje

Cualquier tipo de mensaje puede ser reply a un mensaje anterior — visualmente aparece citado en el chat del contacto. Agregá context.message_id con el waba_message_id del mensaje al que respondés:

POST /v1/messages
{
  "tenant_ref": "pizzeria-don-mario",
  "to": "5491155555555",
  "type": "text",
  "context": { "message_id": "wamid.HBg..." },
  "text": { "body": "Sí, está abierto hasta las 23." }
}

Marcar como leído

POST /v1/messages/mark-read

Le decimos a Meta que tu sistema ya leyó el mensaje del contacto (el contacto va a ver el doble tilde azul). Opcionalmente activa el indicador "escribiendo…".

POST /v1/messages/mark-read
{
  "tenant_ref": "pizzeria-don-mario",
  "waba_message_id": "wamid.HBg...",
  "typing_indicator": { "type": "text" }
}

waba_message_id es el data.message.id del message.received. typing_indicator es opcional y va como objeto — { "type": "text" } es el único tipo que acepta Meta hoy; un true suelto devuelve 422. Sin él, solo marca como leído.

El indicador "escribiendo…" dura ~25 segundos o hasta que mandes el próximo mensaje (lo que ocurra primero). Siempre viaja junto con el leído: es el mismo pedido a Meta, así que no hay forma de mostrar "escribiendo…" sin que el contacto vea el doble tilde azul.

Acuse con reacción. Si además querés reaccionar apenas llega el mensaje (un 👀, por ejemplo), es un POST /v1/messages con type: "reaction" aparte, con el mismo waba_message_id. Son dos llamadas por mensaje recibido más tu respuesta: tenelo en cuenta contra el límite de 60 por minuto de la API key.

Descargar media recibida

GET /v1/media/{id}

Cuando un contacto te manda una foto, audio, video o documento, en el webhook message.received llega un id dentro de la clave del tipo (data.message.image.id, data.message.audio.id, …). Bajalo desde nuestro endpoint — proxiamos con el token de Meta así no tenés que manejarlo vos.

El webhook también trae una url de Meta: no la uses. Esa URL vive 5 minutos y hay que pedirla con el token de Meta del negocio, que no exponemos a nadie. Nosotros pedimos una URL fresca cada vez que llamás a este endpoint, así que del lado tuyo no hay ventana de 5 minutos: guardá el id y bajá el archivo cuando lo necesites.

GET /v1/media/MID01?tenant_ref=pizzeria-don-mario
Authorization: Bearer TU_API_KEY

Respuesta: el binario crudo, con headers:

Si querés sugerir un nombre de archivo para descarga directa:

GET /v1/media/MID01?tenant_ref=pizzeria-don-mario&filename=foto.jpg

Historial de coexistencia

GET /v1/history

Solo en números con coexistencia. Al conectar un negocio que sigue usando la app de WhatsApp Business en el celular, Meta sincroniza los chats previos (en los minutos posteriores al onboarding) y después avisa de cada mensaje que el negocio manda desde el celular. Todo eso queda guardado y lo leés con este endpoint — ideal para minar el historial y armarte el contexto de cada cliente.

GET /v1/history?tenant_ref=pizzeria-don-mario&limit=50
Authorization: Bearer TU_API_KEY

Respuesta:

{
  "tenant_ref": "pizzeria-don-mario",
  "data": [
    {
      "id": 812,
      "wa_message_id": "wamid.HBg...",
      "source": "history",
      "direction": "inbound",
      "from": "5491155555555",
      "to": "5493412004406",
      "peer": "5491155555555",
      "type": "text",
      "status": "DELIVERED",
      "timestamp": "2026-08-01T18:22:10-03:00",
      "message": { "id": "wamid.HBg...", "type": "text", "text": { "body": "¿Hacen envíos?" } }
    }
  ],
  "paging": { "count": 1, "last_id": 812, "has_more": false }
}

Filtros opcionales (query params):

Para recorrerlo entero: pedí sin since_id, guardá el paging.last_id y repetí con since_id=last_id mientras has_more sea true. El orden es por llegada (no cronológico): los chats previos llegan en tandas, así que cada item trae su timestamp original para que ordenes vos.

El sync inicial de Meta llega algunos minutos después del onboarding y en tandas. Si recién conectaste el negocio y esto viene vacío, reintentá en unos minutos. El aviso en tiempo real de "el dueño respondió desde el celular" es el webhook message.echo — este endpoint es la copia persistida, para leer a tu ritmo.

Plantillas

POST /v1/templates

Crear una plantilla en nombre del negocio. Meta la pasa a revisión.

POST /v1/templates
{
  "tenant_ref": "pizzeria-don-mario",
  "name": "pedido_listo",
  "language": "es",
  "category": "UTILITY",
  "components": [
    {
      "type": "BODY",
      "text": "Hola {{1}}, tu pedido #{{2}} ya está listo para retirar.",
      "example": { "body_text": [["Juan", "12345"]] }
    },
    {
      "type": "FOOTER",
      "text": "Sabado Cloud · Notificación automática"
    }
  ]
}

Reglas que aplica Meta:

La respuesta es 201 con la plantilla creada; el status arranca en PENDING — Meta todavía no la revisó. Cómo seguir, abajo.

Qué pasa después de crear una plantilla

Meta revisa en cualquier momento entre minutos y horas. Tenés dos caminos para enterarte, y conviene usar los dos:

GET /v1/templates

Listar las plantillas del negocio.

GET /v1/templates?tenant_ref=pizzeria-don-mario&limit=20

GET /v1/templates/{name}

El estado de una plantilla puntual. Sin language te devuelve todos los idiomas con ese nombre.

GET /v1/templates/pedido_listo?tenant_ref=pizzeria-don-mario&language=es
{
  "tenant_ref": "pizzeria-don-mario",
  "data": [
    {
      "id": "27686102984401620",
      "name": "pedido_listo",
      "language": "es",
      "category": "UTILITY",
      "status": "REJECTED",
      "components": [ ... ],
      "quality_score": null,
      "rejected_reason": "INVALID_FORMAT"
    }
  ]
}

Los status que vas a ver casi siempre son PENDING, APPROVED y REJECTED (Meta también usa PAUSED, DISABLED y algún otro según el caso). Cuando es REJECTED, el motivo está en rejected_reason. Si no existe ninguna plantilla con ese nombre, 404 template_not_found.

DELETE /v1/templates/{name}

Borrar una plantilla.

DELETE /v1/templates/pedido_listo?tenant_ref=pizzeria-don-mario

Errores al crear una plantilla

Cuando Meta rechaza una plantilla casi siempre devuelve un message genérico ("Invalid parameter", meta_code: 100) y pone el motivo concreto aparte. Te lo pasamos en meta_detail (y el subcódigo en meta_subcode): leé ese campo para saber qué corregir.

{
  "ok": false,
  "error": "meta_template_invalid",
  "message": "Invalid parameter — Body text contains variables without examples.",
  "meta_code": 100,
  "meta_subcode": 2388043,
  "meta_detail": "Body text contains variables without examples."
}

Causas frecuentes de meta_code: 100:

No es un bloqueo de cuenta: crear plantillas no requiere business verification. Si todas tus plantillas fallan con el mismo code 100, el problema está en el payload, no en permisos — mirá meta_detail.

Perfil del negocio

Datos visibles cuando el contacto toca el nombre del negocio dentro de WhatsApp: descripción, sitio web, rubro, etc. El cliente final también lo puede editar desde su panel.

GET /v1/business-profile

GET /v1/business-profile?tenant_ref=pizzeria-don-mario
Authorization: Bearer TU_API_KEY

Respuesta:

{
  "tenant_ref": "pizzeria-don-mario",
  "profile": {
    "about": "La mejor pizza al horno de barro.",
    "address": "Av. Corrientes 1234, CABA",
    "description": "Pizzería tradicional desde 1985...",
    "email": "contacto@donmario.com.ar",
    "vertical": "RESTAURANT",
    "websites": ["https://donmario.com.ar"],
    "profile_picture_url": "https://...",
    "messaging_product": "whatsapp"
  }
}

PUT /v1/business-profile

Mandá solo los campos que querés cambiar. Lo que no mandes queda intacto.

PUT /v1/business-profile
{
  "tenant_ref": "pizzeria-don-mario",
  "about": "Pizza al horno de barro · Delivery 30 min",
  "vertical": "RESTAURANT",
  "websites": ["https://donmario.com.ar"]
}

Reglas de Meta:

Para cambiar la foto de perfil hay que subir el binario al endpoint de Meta y mandar el handle resultante. Por ahora no lo exponemos — si lo necesitás, escribinos.

Estado del número

Diagnostics en vivo del número conectado del negocio: calidad, capacidad, throughput y salud. El cliente final ve lo mismo en su panel.

GET /v1/phone-number

GET /v1/phone-number?tenant_ref=pizzeria-don-mario
Authorization: Bearer TU_API_KEY

Respuesta:

{
  "tenant_ref": "pizzeria-don-mario",
  "phone_number": {
    "display_phone_number": "+54 9 11 5555-1234",
    "verified_name": "Pizzería Don Mario",
    "quality_rating": "GREEN",
    "throughput": { "level": "STANDARD" },
    "health_status": {
      "entities": [
        { "entity_type": "PHONE_NUMBER", "can_send_message": "AVAILABLE" }
      ]
    },
    "messaging_limit_tier": "TIER_1K",
    "name_status": "APPROVED",
    "code_verification_status": "VERIFIED",
    "platform_type": "CLOUD_API",
    "last_onboarded_time": "2026-04-23T18:22:00+0000"
  }
}
CampoValoresQué significa
quality_ratingGREEN / YELLOW / REDCalidad percibida por los contactos. RED limita envíos.
messaging_limit_tierTIER_50 / 250 / 1K / 10K / 100K / UNLIMITEDCantidad de conversaciones únicas por día.
throughput.levelSTANDARD / HIGH / NOT_APPLICABLEVelocidad máxima de envío.
health_status.entities[].can_send_messageAVAILABLE / LIMITED / BLOCKEDEstado operativo de cada entidad (PHONE_NUMBER, WABA, BUSINESS).

POST /v1/phone-number/register

Re-registra el número contra Meta. Útil para destrabar errores 131031 Business Account locked en Test Numbers, o para reanudar uso de un número que fue desactivado.

POST /v1/phone-number/register
{
  "tenant_ref": "pizzeria-don-mario",
  "pin": "000000"
}

Si el número tiene 2FA activado, el pin es obligatorio y son los 6 dígitos del 2FA. Para Test Numbers de Meta el default es 000000.

Respuesta:

{ "ok": true }

Errores específicos:

Estadísticas

Métricas históricas de conversaciones y plantillas, directo del WABA del negocio. Las fechas se mandan como Unix timestamp en segundos.

GET /v1/analytics/conversations

Volumen y costo de conversaciones en un rango temporal.

GET /v1/analytics/conversations
  ?tenant_ref=pizzeria-don-mario
  &start=1714521600
  &end=1717113600
  &granularity=DAILY

Respuesta:

{
  "tenant_ref": "pizzeria-don-mario",
  "analytics": {
    "data_points": [
      { "start": 1714521600, "end": 1714608000, "conversation": 47, "cost": 0.94 },
      { "start": 1714608000, "end": 1714694400, "conversation": 63, "cost": 1.26 }
    ]
  }
}

Parámetros opcionales para filtrar:

ParámetroValores
granularityHALF_HOUR, DAILY, MONTHLY (default DAILY)
phone_numbers[]Filtrar por display phone numbers específicos
country_codes[]ISO 3166-1 alpha-2 (ej AR, BR)
metric_types[]COST, CONVERSATION
conversation_categories[]AUTHENTICATION, MARKETING, SERVICE, UTILITY
conversation_types[]FREE_ENTRY_POINT, FREE_TIER, REGULAR
conversation_directions[]BUSINESS_INITIATED, USER_INITIATED

GET /v1/analytics/templates

Métricas por plantilla: enviados, entregados, leídos, clicks. Hasta 10 templates por request.

GET /v1/analytics/templates
  ?tenant_ref=pizzeria-don-mario
  &start=1714521600
  &end=1717113600
  &template_ids[]=TPL1
  &template_ids[]=TPL2
  &metric_types[]=SENT
  &metric_types[]=DELIVERED
  &metric_types[]=READ
  &metric_types[]=CLICKED

Respuesta:

{
  "tenant_ref": "pizzeria-don-mario",
  "analytics": {
    "data": [
      {
        "template_id": "TPL1",
        "data_points": [
          { "start": 1714521600, "sent": 120, "delivered": 118, "read": 95 }
        ]
      }
    ]
  }
}

Los template_ids los obtenés con GET /v1/templates — cada item tiene un id de Meta.