Files
whatsapp-api-mockup/docs/research-api.md
T
2026-09-27 19:17:33 -06:00

426 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# WhatsApp Business Platform (Cloud API) — Tipos de mensajes, payloads y límites
> Investigación sobre la documentación oficial de Meta (developers.facebook.com), 2026-09-26.
> Nota: Meta migró la doc de `/docs/whatsapp/cloud-api/...` a `/documentation/business-messaging/whatsapp/...`.
> Los tipos text/media/location/contacts siguen accesibles en las URLs legacy; interactive/templates ya
> solo viven en la nueva ruta. Los datos citados provienen de las páginas listadas en "Fuentes".
## 1. Convención común de todos los mensajes
Endpoint: `POST https://graph.facebook.com/<API_VERSION>/<PHONE_NUMBER_ID>/messages`
con header `Authorization: Bearer <TOKEN>` y `Content-Type: application/json`
(Fuente: Message API reference — §Fuentes [17]).
Sobre (envelope) común a todo mensaje (BaseMessageProperties, [17]):
```json
{
"messaging_product": "whatsapp", // requerido, siempre "whatsapp"
"recipient_type": "individual", // "individual" (o "group" para grupos)
"to": "<número del usuario>", // requerido
"type": "<text|image|video|audio|document|sticker|location|contacts|interactive|template|reaction>",
"context": { "message_id": "wamid..." } // opcional: responde a un mensaje previo
}
```
Respuesta de éxito: `contacts[].input`, `contacts[].wa_id` y `messages[].id` (`wamid.HBgL...`);
el `message_status` inicial puede ser `accepted` (también existen `held_for_quality_assessment` y `paused`) [17].
## 2. Text
Payload [1]:
```json
{
"messaging_product": "whatsapp",
"recipient_type": "individual",
"to": "16505551234",
"type": "text",
"text": {
"preview_url": false,
"body": "Hola mundo"
}
}
```
| Campo | Descripción | Límite |
|---|---|---|
| `text.body` | Texto del mensaje (requerido). El cliente hipervincula URLs automáticamente | **4096 caracteres** |
| `text.preview_url` | `true` = el cliente intenta renderizar preview del enlace | opcional |
Vista en el teléfono: burbuja de solo texto; con `preview_url: true` **solo el primer URL** del body
recibe preview (requiere `http://` o `https://`); si no se puede obtener, queda como enlace clicable [1].
El preview usa el Open Graph de la página: `og:title` (negrita, máx 2 líneas), `og:description`
(~80 caracteres), `og:url` y `og:image` (thumbnail <600 KB, ≥300 px de ancho) renderizados encima
del texto de la burbuja [22].
**Formato de texto** (`*negrita*`, `_cursiva_`, `~tachado~`, ` ```mono``` `):
la página oficial actual de text-messages **ya no documenta la sintaxis** de formateo (verificado [1]);
lo único oficial vigente leído es que el body/footer interactivos "soportan emojis y markdown" [17]
y que el header de plantillas tipo TEXT **prohíbe** caracteres markdown [20]. La sintaxis con
`*asteriscos*`, `_guiones bajos_`, `~tildes~` y triple acento grave es la estándar del cliente
WhatsApp, pero su referencia oficial en el site de developers está retirada: dato NO verificable
contra fuentes oficiales en esta investigación.
## 3. Media (image, video, audio, document, sticker)
Patrón común: el objeto de media admite `id` (asset subido vía Media API — recomendado por
performance) **o** `link` (URL pública — "no recomendado"); se provee solo uno [2]–[6].
**Image** [2] — `type: "image"`, objeto `image`: `{ id | link, caption }`.
Caption máx **1024 caracteres**. Formatos: JPEG (`image/jpeg`) y PNG (`image/png`),
8-bit RGB/RGBA, máx **5 MB**.
**Video** [3] — `type: "video"`, objeto `video`: `{ id | link, caption }`.
Caption máx **1024**. Solo códecs **H.264 (video) + AAC (audio)**, un solo stream de audio o ninguno;
MP4 (`video/mp4`) y 3GP (`video/3gpp`), máx **16 MB**. H.264 "High" con B-frames no funciona en
Android; usar Main/Baseline y `moov` antes de `mdat` (ffmpeg `-movflags faststart`).
**Audio** [4] — `type: "audio"`, objeto `audio`: `{ id | link, voice }`.
`voice: true` = mensaje de voz (requiere OGG con codec OPUS, mono; el OGG base no es soportado).
Formatos: AAC, AMR, MP3 (`audio/mpeg`), M4A (`audio/mp4`), OGG (`audio/ogg`); máx **16 MB**.
Vista: archivos ≤512 KB muestran icono de play como mensaje de voz; los mayores muestran icono de descarga [4].
**Document** [5] — `type: "document"`, objeto `document`: `{ id | link, caption, filename }`.
Caption máx **1024**; `filename` (con extensión) define el icono del tipo de archivo en el cliente.
Tipos soportados (máx **100 MB**): TXT, XLS/XLSX, DOC/DOCX, PPT/PPTX, PDF. Otros tipos pueden
enviarse pero no están soportados oficialmente.
**Sticker** [6] — `type: "sticker"`, objeto `sticker`: `{ id | link }` (sin caption).
Formato **WebP** únicamente: animado máx **500 KB**, estático máx **100 KB**.
Vista: se muestra como sticker (sin texto); error si el WebP no es soportado o excede el tamaño.
Vista general media: la burbuja muestra el recurso y, si aplica, el caption debajo; en document
aparece icono por extensión + filename; en image/video el caption va bajo el recurso [2][3][5].
## 4. Location
Payload [7] — `type: "location"`, objeto `location`:
| Campo | Requerido | Ejemplo |
|---|---|---|
| `latitude` | sí | `"37.44216251868683"` (grados decimales) |
| `longitude` | sí | `"-122.16153582049394"` |
| `name` | no | `"Philz Coffee"` |
| `address` | no | `"101 Forest Ave, Palo Alto, CA 94301"` |
Vista: tarjeta de mapa con las coordenadas; nombre y dirección se muestran como título/detalle.
La página no especifica límites de caracteres ni renderizado adicional [7].
## 5. Contacts
Payload [8] — `type: "contacts"`, array `contacts[]` de objetos contacto.
Cada mensaje puede incluir **hasta 257 contactos** (se recomienda menos por usabilidad).
Único campo requerido por contacto: `name.formatted_name` (es lo que aparece en la burbuja
junto al botón de flecha de perfil). Resto opcionales [8]:
- `name`: `formatted_name`, `first_name`, `last_name`, `middle_name`, `suffix`, `prefix`
- `phones[]`: `phone`, `type` (CELL/MAIN/IPHONE/HOME/WORK), `wa_id` — si falta `wa_id`,
el botón Message/Save se sustituye por **"Invite to WhatsApp"**
- `emails[]`: `email`, `type` (PERSONAL/WORK)
- `urls[]`: `url`, `type` (COMPANY/WORK/PERSONAL/FACEBOOK_PAGE/INSTAGRAM)
- `org`: `company`, `department`, `title`
- `addresses[]`: `street`, `city`, `state`, `zip`, `country`, `country_code` (ISO-2), `type`
- `birthday`: formato `YYYY-MM-DD`
Vista: tarjeta de contacto con avatar, nombre formateado y filas por cada dato presente;
algunos metadatos (direcciones, cumpleaños) pueden no mostrarse según el dispositivo del usuario [8].
## 6. Interactive
`type: "interactive"` + objeto `interactive` con `type` de: `button`, `list`, `cta_url`,
`product`, `product_list`, `catalog_message`, `carousel`, `flow` (y `call_permission_request`) [17].
Objetos internos: `header` (`HeaderObject`: text/image/video/document; requerido para `product_list`,
**no permitido** en `product`), `body` (`text`, requerido — "emojis y markdown soportados"),
`footer` (`text`, opcional — "emojis, markdown y links soportados"), `action` (estructura varía por tipo) [17].
### 6.1 Botones reply (quick replies) — `interactive.type: "button"` [10]
```json
{ "type": "interactive",
"interactive": {
"type": "button",
"header": { "type": "text", "text": "Detalles" },
"body": { "text": "¿Confirmas tu cita?" },
"footer": { "text": "Elige una opción" },
"action": { "buttons": [
{ "type": "reply", "reply": { "id": "id_si", "title": "Sí" } },
{ "type": "reply", "reply": { "id": "id_no", "title": "No" } } ] } } }
```
| Campo | Límite |
|---|---|
| Botones | **máx 3** |
| `reply.title` | **20 caracteres**, único entre botones |
| `reply.id` | **256 caracteres**, único por botón |
| `body.text` | **1024 caracteres** (URLs se hipervinculan) |
| `footer.text` | **60 caracteres** |
| `header` | tipos text/image/video/document (límite de header text no declarado en esta página) [10] |
Vista: header arriba (media o texto), body, footer y los botones como filas-blanco fijas bajo la
burbuja. Al tocar, webhook `messages` con `button_reply.id`/`title` + `context.id` del mensaje original [10].
### 6.2 Lista — `interactive.type: "list"` [9]
```json
{ "type": "interactive",
"interactive": {
"type": "list",
"header": { "type": "text", "text": "Menú" },
"body": { "text": "Selecciona un producto" },
"footer": { "text": "Catálogo 2026" },
"action": { "button": "Ver opciones",
"sections": [
{ "title": "Bebidas",
"rows": [ { "id": "r1", "title": "Café", "description": "Chico" } ] } ] } } }
```
| Campo | Límite |
|---|---|
| Secciones | **mín 1, máx 10** |
| Filas (total, sumando secciones) | **máx 10** |
| `action.button` (etiqueta que despliega) | **20 caracteres** |
| `header.text` | **60 caracteres** (solo type text) |
| `body.text` | **4096 caracteres** |
| `footer.text` | **60 caracteres** |
| Título de sección | **24 caracteres** (requerido solo si hay >1 sección, [17]) |
| `row.title` | **24 caracteres** |
| `row.description` | opcional, **72 caracteres** |
| `row.id` | **200 caracteres** |
Vista: burbuja con header/body/footer y **un único botón** (`action.button`) que abre la lista
de opciones seleccionables a pantalla (las filas muestran title y description); el webhook devuelve
`list_reply` con `id`, `title`, `description` + `context` [9].
### 6.3 CTA URL — `interactive.type: "cta_url"` [11]
```json
"interactive": {
"type": "cta_url",
"header": { "type": "image", "image": { "link": "https://…/promo.jpg" } },
"body": { "text": "Aprovecha la promo" },
"footer": { "text": "Vence hoy" },
"action": { "name": "cta_url",
"parameters": { "display_text": "Abrir tienda", "url": "https://mitienda.com" } } }
```
Límites: `display_text` **20 caracteres**; `body.text` **1024**; `footer.text` **60**;
`header.text` **60**. Header opcional de tipo image/video/document/text. Un solo botón URL por
acción; la doc menciona etiquetas únicas "si se usan múltiples botones" sin declarar máximo [11].
Vista: media/texto del header, body, footer y un botón azul que abre el navegador con la URL [11].
### 6.4 Producto único — `interactive.type: "product"` [15]
```json
"interactive": {
"type": "product",
"body": { "text": "Texto opcional" },
"footer": { "text": "Texto opcional" },
"action": { "catalog_id": "123456789", "product_retailer_id": "SKU-001" } }
```
`action` requiere `catalog_id` + `product_retailer_id` (SKU del producto); `header` **no permitido**
en este tipo [15][17]; body/footer opcionales. Sin límites de caracteres declarados en la página [15].
Vista: tarjeta con imagen/nombre/precio actuales del producto; al tocar se abre una
**Product Detail Page (PDP)** con los datos en tiempo real (solo imágenes; videos/GIF no se muestran);
hay botón para escribir al negocio; reenviable, no puede enviarse como notificación [15].
### 6.5 Multi-producto — `interactive.type: "product_list"` [16]
```json
"interactive": {
"type": "product_list",
"header": { "type": "text", "text": "Nuestra tienda" },
"body": { "text": "Elige productos" },
"footer": { "text": "Envío gratis" },
"action": { "catalog_id": "123456789",
"sections": [ { "title": "Cafés",
"product_items": [ { "product_retailer_id": "SKU-001" } ] } ] } }
```
Requeridos: header type **text**, body, `action.catalog_id` + `sections[]` (`title` +
`product_items[].product_retailer_id`). Límite documentado: **hasta 30 productos** en secciones;
máximo de secciones y límites de texto NO declarados en la página (el título de sección es el
`SectionObject.title` de máx 24 chars según [17]). Si el SKU no existe se descarta y llega
error pidiendo actualización de catálogo [16]. Vista: carrusel horizontal de productos que abre
PDP y permite armar carrito por dispositivo (el carrito no sincroniza entre dispositivos) [16].
### 6.6 Catálogo — `interactive.type: "catalog_message"` [14]
```json
"interactive": {
"type": "catalog_message",
"body": { "text": "Explora nuestro catálogo" },
"action": { "name": "catalog_message",
"parameters": { "thumbnail_product_retailer_id": "SKU-001" } },
"footer": { "text": "Tienda oficial" } }
```
`body.text` máx **1024**; `footer.text` máx **60**; `thumbnail_product_retailer_id` opcional
(define la miniatura; si falta se usa la imagen del primer artículo del catálogo) [14].
Vista: miniatura de producto, texto fijo de encabezado, body, footer y botón **"View catalog"**
que abre el catálogo completo dentro de WhatsApp [14].
### 6.7 Carrusel de productos — `interactive.type: "carousel"` (cards tipo product) [13]
```json
"interactive": {
"type": "carousel",
"body": { "text": "Lo más vendido" },
"action": { "cards": [
{ "card_index": 0, "type": "product",
"action": { "product_retailer_id": "SKU-001", "catalog_id": "123456789" } } ] } }
```
Límites: **2 a 10 cards**; `card_index` entero **0–9**, sin repetir; todas las cards deben
referenciar el **mismo `catalog_id`**; `body.text` máx **1024**. **Sin header, footer ni botones**
a nivel de mensaje [13]. Vista: tarjetas desplazables horizontalmente, cada una abre su PDP [13][15].
### 6.8 Carrusel de media — cards con header image/video [12]
```json
"interactive": {
"type": "carousel",
"body": { "text": "Nuevos cursos" },
"action": { "cards": [
{ "card_index": 0,
"header": { "type": "image", "image": { "link": "https://…/a.jpg" } },
"body": { "text": "Curso A" },
"action": { "name": "cta_url",
"parameters": { "display_text": "Inscribirme", "url": "https://…" } } } ] } }
```
Límites exactos [12]: **2–10 cards**; header por card **solo image o video** (`link` o `id`);
body por card máx **160 caracteres y 2 saltos de línea**; body del mensaje máx **1024**;
quick-reply `id` máx **256**, etiquetas (quick reply y URL) máx **20**; cada card lleva **un botón
URL o uno o más quick replies**; tipos y cantidades de botones deben ser idénticos en todas las cards.
Sin header/footer a nivel de mensaje. Vista: cards con media arriba, texto y botón, desplazables
izquierda→derecha desde índice 0 [12].
## 7. Template messages
Único tipo enviable **fuera de la ventana de servicio** al cliente; se crean vía Template API o
WhatsApp Manager y deben quedar `APPROVED` antes de enviarse [19]. Creación: `name` (máx 512,
minúsculas/guiones bajos), `category` (AUTHENTICATION/MARKETING/UTILITY), `language`,
`parameter_format` (`named` p.ej. `{{first_name}}`, o `positional` p.ej. `{{1}}`) y `components`
[19]. Límites de cuenta: **250 plantillas/WABA** (6000 con portafolio verificado + display name
aprobado), **100 plantillas creadas por hora** por WABA [19].
### 7.1 Envío por Cloud API [21]
```json
{ "messaging_product": "whatsapp",
"recipient_type": "individual",
"to": "16505551234",
"type": "template",
"template": {
"name": "reservation_confirmation",
"language": { "code": "en_US" },
"components": [
{ "type": "header",
"parameters": [ { "type": "image", "image": { "id": "2871834006348767" } } ] },
{ "type": "body",
"parameters": [ { "type": "text", "parameter_name": "day", "text": "Saturday" } ] },
{ "type": "buttons",
"sub_type": "URL", "index": 0,
"parameters": [ { "type": "text", "text": "abc123" } ] } ] } }
```
El header solo se envía si la plantilla usa header de media (image/video/document; los header
text/location no llevan parámetros en el envío) [21]. En plantillas posicionales los parámetros
van por orden, sin `parameter_name` [21]. En webhook los botones responden con
`button_reply`/`list_reply`; COPY_CODE entrega el código y CATALOG/MPM abren catálogo (los
sub_types de botón definidos por la API son: `CATALOG`, `COPY_CODE`, `FLOW`, `MPM`, `OTP`,
`PHONE_NUMBER`, `QUICK_REPLY`, `URL` [18]).
### 7.2 Componentes y límites de caracteres (creación) [20]
| Componente | Formatos / tipos | Límites |
|---|---|---|
| **HEADER** (1 máx) | `format`: TEXT, IMAGE, VIDEO, DOCUMENT, LOCATION | TEXT: **60 caracteres**, sin markdown, admite 1 parámetro. Media: handle de Resumable Upload API (GIF mp4 ≤3.5 MB, solo Marketing). LOCATION: sin parámetros al crear; al enviar `latitude`, `longitude`, `name`, `address`; solo UTILITY/MARKETING |
| **BODY** (requerido, 1) | texto | **1024 caracteres**; parámetros múltiples named/posicionales con ejemplos obligatorios |
| **FOOTER** (opcional, 1) | texto | **60 caracteres** |
| **BUTTONS** (array `buttons`) | ver abajo | **máx 10 botones en total** por plantilla |
Botones por tipo [20]:
- **QUICK_REPLY**: hasta **10**; etiqueta máx **25 caracteres**; deben agruparse (no intercalar
`QR, URL, QR`); el orden en el teléfono lo define `index` al enviar.
- **URL**: hasta **2**; etiqueta 25; `url` máx **2000 caracteres**, admite **1 variable** al final
(requiere `example`; valores con caracteres especiales deben ir percent-encoded: `%20`, `%3A`,
`%7C`… o el envío falla). El título corto del botón puede sobreescribirse (`tap-target-url-title-override`).
- **PHONE_NUMBER**: solo **1**; `phone_number` máx **20 caracteres** (se recortan ceros a la
izquierda tras el código de país); etiqueta 25.
- **COPY_CODE**: solo **1**; código copiable en `example`, **máx 20 caracteres**.
- **CATALOG**: abre el catálogo; **MPM** (multi-producto) muestra hasta **30 productos en hasta
10 secciones**; ambos no personalizables.
- **VOICE_CALL**: inicia llamada de WhatsApp al negocio; **OTP** (URL/ONE_TAP/ZERO_TAP) para
plantillas de autenticación, con `autofill_text`, `package_name`, `signature_hash`.
Rendimiento en pantalla [20]: con **>3 botones** WhatsApp muestra 2 y un botón **"See all options"**
que expande el resto; plantillas con **4+ botones**, o quick replies mezcladas con otros tipos,
**no se ven en clientes de escritorio**. Vista general: header arriba del body; body con variables
resaltadas; footer en gris pequeño; botones como filas fijas al pie de la burbuja [20].
### 7.3 Plantillas de carrusel (marketing)
Existen `media-card-carousel-templates` (cards de media) y `product-card-carousel-template-messages`
[19]; su estructura de componentes en la Template API incluye `type: CAROUSEL` y
`LIMITED_TIME_OFFER` [18]. Detalle no extraído en esta investigación (fuera del alcance directo).
## 8. Tabla resumen de límites clave
| Elemento | Límite | Fuente |
|---|---|---|
| `text.body` / body de lista | 4096 | [1][9] |
| Caption de image/video/document | 1024 | [2][3][5] |
| Tamaño image / video / audio / document | 5 MB / 16 MB / 16 MB / 100 MB | [2][3][4][5] |
| Sticker WebP animado / estático | 500 KB / 100 KB | [6] |
| Contactos por mensaje | 257 | [8] |
| Botones reply / list rows / secciones | 3 / 10 / 10 | [10][9] |
| Título botón (interactive) / display_text | 20 / 20 | [10][11][12] |
| `reply.id` / `row.id` | 256 / 200 | [10][9] |
| Header / footer interactivos | 60 / 60 | [9][11] |
| Section title / row title / row description | 24 / 24 / 72 | [9][17] |
| Body de card de carrusel de media | 160 (2 saltos) | [12] |
| Cards de carrusel | 2–10 (índices 0–9) | [12][13] |
| Productos por product_list / MPM | 30 (en ≤10 secciones) | [16][20] |
| Plantilla: header TEXT / body / footer | 60 / 1024 / 60 | [20] |
| Plantilla: botones totales / QR / URL / PHONE / COPY_CODE | 10 / 10 / 2 / 1 / 1 | [20] |
| Plantilla: etiqueta botón / URL / phone / código | 25 / 2000 / 20 / 20 | [20] |
| Plantillas por WABA / creación por hora | 250 (6000 verificada) / 100 | [19] |
## 9. Fuentes consultadas (oficiales, developers.facebook.com)
[1] https://developers.facebook.com/docs/whatsapp/cloud-api/messages/text-messages
[2] https://developers.facebook.com/docs/whatsapp/cloud-api/messages/image-messages
[3] https://developers.facebook.com/docs/whatsapp/cloud-api/messages/video-messages
[4] https://developers.facebook.com/docs/whatsapp/cloud-api/messages/audio-messages
[5] https://developers.facebook.com/docs/whatsapp/cloud-api/messages/document-messages
[6] https://developers.facebook.com/docs/whatsapp/cloud-api/messages/sticker-messages
[7] https://developers.facebook.com/docs/whatsapp/cloud-api/messages/location-messages
[8] https://developers.facebook.com/docs/whatsapp/cloud-api/messages/contacts-messages
[9] https://developers.facebook.com/documentation/business-messaging/whatsapp/messages/interactive-list-messages
[10] https://developers.facebook.com/documentation/business-messaging/whatsapp/messages/interactive-reply-buttons-messages
[11] https://developers.facebook.com/documentation/business-messaging/whatsapp/messages/interactive-cta-url-messages
[12] https://developers.facebook.com/documentation/business-messaging/whatsapp/messages/interactive-media-carousel-messages.md
[13] https://developers.facebook.com/documentation/business-messaging/whatsapp/catalogs/interactive-product-carousel-messages.md
[14] https://developers.facebook.com/documentation/business-messaging/whatsapp/catalogs/catalog-messages.md
[15] https://developers.facebook.com/documentation/business-messaging/whatsapp/catalogs/single-product-messages.md
[16] https://developers.facebook.com/documentation/business-messaging/whatsapp/catalogs/multi-product-messages.md
[17] https://developers.facebook.com/documentation/business-messaging/whatsapp/reference/whatsapp-business-phone-number/message-api.md
[18] https://developers.facebook.com/documentation/business-messaging/whatsapp/reference/whatsapp-business-account/message-template-api.md
[19] https://developers.facebook.com/documentation/business-messaging/whatsapp/templates/overview.md
[20] https://developers.facebook.com/documentation/business-messaging/whatsapp/templates/components.md
[21] https://developers.facebook.com/documentation/business-messaging/whatsapp/templates/utility-templates/utility-templates.md
[22] https://developers.facebook.com/documentation/business-messaging/whatsapp/link-previews.md
**Declaraciones de cobertura**: (a) la sintaxis de formateo de texto (*bold*, _italic_, ~tachado~,
mono) ya no está documentada en las páginas oficiales consultadas — solo consta la nota de que
body/footer interactivos soportan markdown [17] y la prohibición de markdown en header TEXT de
plantillas [20]; (b) límites no declarados: máximo de secciones en product_list, límites de texto
en `product`, límite de header text en `interactive.type: button`, y máximo de botones en
`cta_url` — se reportan como no documentados, no como valores asumidos; (c) varias páginas nuevas
(carousel-messages, product-messages en su slug HTML) solo renderizan con JavaScript y se accedió
a su contenido vía las versiones `.md` publicadas por Meta en el índice `llms.txt`.