Mensajes
Todo lo que podés mandar y recibir en una conversación 1:1, qué responde cada envío y cómo saber qué puede hacer cada número.
Todo lo que podés mandar y recibir en una conversación 1:1, con la misma llamada para un número oficial de Meta y para uno por QR.
| Pieza | Estado |
|---|---|
Recibir mensajes (message.received) y leer el historial | Disponible |
| Enviar texto, archivos, botones, listas, plantillas, formularios de WhatsApp, tarjetas de contacto, reacciones y reenvíos | Disponible, con el permiso conversations:send y las reglas de WhatsApp |
Saber qué pasó con cada envío (message.status_changed y la consulta del mensaje) | Disponible: Estados de mensaje |
| Ubicación, nota de voz, encuestas e invitaciones a eventos | Todavía no hay una ruta para mandarlos |
Qué podés mandar
| Tipo | Cómo | En capabilities.messages |
|---|---|---|
| Texto | Enviar un mensaje a un contacto, con text | text |
| Imagen, video, documento, audio o sticker | La misma ruta, con file: la URL pública del archivo (abajo) | media |
| Botones o lista | La misma ruta, con options (de 1 a 10, cada una con id y label) | buttons, lists |
| Plantilla aprobada | Enviar una plantilla | templates |
| Formulario de WhatsApp | Enviar un formulario, solo por un número de WhatsApp Cloud | — |
| Tarjeta de contacto | Enviar una tarjeta de contacto | contact_card |
| Reacción | Reaccionar a un mensaje | reactions |
| Reenvío | Reenviar un mensaje | — |
Qué puede cada número lo dice su ficha. Listar números trae en cada uno capabilities.messages, con un valor verdadero o falso por tipo: miralo antes de mandar en lugar de suponerlo por el tipo de número (Números). La ficha también tiene location, voice_note, polls y events, pero hoy la API no tiene una ruta para mandarlos.
- Todo envío pide
conversations:sendy pasa por las reglas de WhatsApp: la ventana de 24 horas en los números de WhatsApp Cloud, los frenos contra spam y el ritmo por número (hasta 60 mensajes por segundo en un número de WhatsApp Cloud, 15 en coexistencia y sin límite por QR). - Por la API solo se les escribe a personas: si el contacto es un grupo, una lista de difusión o un canal, responde
422 recipient_unsupported. - Recomendamos el número oficial (por qué).
Un texto
curl -X POST "https://api.funnelchat.com/api/v1/contacts/2001/message" \
-H "Authorization: Bearer <id>|fc_live_..." \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{
"device_id": 3,
"contact_id": 2001,
"text": "¡Hola! Te paso la info…",
"client_reference": "pedido-1001"
}'device_ides el número desde el que sale (Listar números).contact_idva también en el cuerpo: la especificación lo pide ahí, además de en la ruta.- Responde
200conmessage: el mensaje ya registrado, con suidystatus: "queued". La llamada no espera a WhatsApp: el mensaje sale enseguida, desde la cola. En cola no es enviado, y un mensajequeuedno se reintenta: reenviarlo lo duplicaría. Qué pasó después te lo cuenta Estados de mensaje. client_reference(opcional, de 1 a 64 caracteres visibles, sin espacios) es tu propia referencia del envío: vuelve en el historial del contacto y enmessage.status_changed. No es única ni secreta (la ven todas las claves y todas las suscripciones de la cuenta): relacionala siempre junto conmessage_idycontact_id, y no pongas datos personales.- Si una regla de WhatsApp frena el envío, responde
422o429con su código y no se registra nada (Reglas de WhatsApp). - Un rechazo por el límite de contactos activos (MAC) es distinto: queda en el historial del contacto un mensaje con estado de error que lleva tu
client_reference. Si tu cuenta está en bloqueo (Ver el consumo de MACs respondestate=enforcing) y el contacto es inactivo (is_mac_inactiveentrue), el envío responde429conmac_lockout_24h. No traeRetry-After: vuelve a funcionar cuando el contacto deja de ser inactivo, al sumar capacidad o al empezar un ciclo nuevo con cupo. A los contactos que ya son activos en el ciclo les seguís escribiendo con normalidad.
Un archivo por URL
No subís el archivo a Funnelchat: en file mandás la URL pública del archivo y WhatsApp o el proveedor lo descarga desde ahí.
curl -X POST "https://api.funnelchat.com/api/v1/contacts/2001/message" \
-H "Authorization: Bearer <id>|fc_live_..." \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{
"device_id": 3,
"contact_id": 2001,
"file": "https://cdn.example.com/facturas/2026-10.pdf",
"text": "Te paso la factura"
}'- El tipo sale de la extensión de la URL:
.jpg,.jpegy.pngson imagen;.webp, sticker;.ogg,.oga,.mp3,.m4a,.aac,.opusy.wav, audio;.gify.mp4, video;.doc,.docxy.pdf, documento. Si la URL no termina en una de esas (por ejemplo una URL firmada sin extensión), mandáfile_type(image,video,audio,documentosticker); si no, responde422. Una extensión de otro formato, como.xlsx, sinfile_typeresponde415. file_namees el nombre con el que el contacto ve un documento, con su extensión (factura.pdf). Es obligatorio en un documento cuya URL no termina en una extensión de la lista.- La URL tiene que durar al menos una hora: un envío con clave puede esperar en cola hasta 54 minutos, y Funnelchat no la abre al recibir tu llamada. Un archivo inaccesible, vencido o fuera de los límites termina en un mensaje
failedcon el error del proveedor. - No pongas secretos en la URL. La URL completa, con su consulta, queda en el historial del contacto, la ven los usuarios de tu cuenta y se copia al reenviar. Usá URLs firmadas de corta duración (cerca de una hora) y para un solo archivo.
- Tamaños máximos de WhatsApp: imagen 5 MB, video y audio 16 MB, documento 100 MB, sticker 100 KB (500 KB si es animado). Los formatos aceptados de cada tipo están en la página del endpoint.
Una plantilla
Fuera de la ventana de 24 horas, un número de WhatsApp Cloud solo puede mandar plantillas aprobadas por Meta. Elegí una en Listar plantillas (su status dice si está aprobada) y mandala con su id en la ruta:
curl -X POST "https://api.funnelchat.com/api/v1/devices/3/templates/45/send-template" \
-H "Authorization: Bearer <id>|fc_live_..." \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{
"phone": "5491100000000",
"contact_id": 2001,
"template_variables": { "body": ["Lucía"] },
"client_reference": "recordatorio-1001"
}'- Responde
200conmessage, en cola, igual que un texto. - Si una variable se completa con un campo personalizado que no existe en tu cuenta, responde
422 template_unresolved_placeholderconplaceholdersycount, y no manda nada. Si la plantilla no existe en tu cuenta,404 template_not_found. - Cuando Meta aprueba, rechaza o pausa una plantilla, te llega
template.status_changed(catálogo).
Un formulario de WhatsApp
Enviar un formulario le manda al contacto un formulario ya publicado, con form_id y el número de WhatsApp Cloud por el que sale (device_id); text y button_text son opcionales. Solo funciona dentro de la ventana de 24 horas. Responde 200 con el mensaje en cola, y lo que el contacto contestó te llega en el evento whatsapp_form.completed, con answers. Sus errores propios (form_not_found, form_not_published, form_not_on_number, number_not_eligible…) están en la página del endpoint.
Crear y publicar formularios todavía no se puede por la URL base: se hace desde Funnelchat.
Qué recibís
- Cada mensaje entrante llega a tu servidor como
message.received, firmado: el mensaje (id,type,text,media), el contacto (id,name,phone), el teléfono del número que lo recibió (receiver.phone) y sudevice_id. Paso a paso: Recibir mensajes. - El historial de un contacto está en Listar los mensajes de un contacto, con paginación por cursor y
updated_sincepara traer solo lo nuevo. Paso a paso: Leer conversaciones. - Un mensaje por su id, con su estado y tu
client_reference: Ver un mensaje. - Qué pasó con cada mensaje que mandaste (enviado, entregado, leído, falló): Estados de mensaje.
Probalo acá
Esto manda un mensaje real
Sale por WhatsApp desde tu número y pasa por las mismas reglas que tu código.
Permiso requerido: conversations:send
httpAuthorizationBearer <clave>contact*integerapplication/json- body
phone?string1 <= lengthwhatsapp_id?string1 <= lengthdevice_id*integertext?stringcontact_id*integerfile?stringArchivo del mensaje: el nombre de un archivo ya subido a tu cuenta o la URL pública de un archivo (te recomendamos https://), que WhatsApp o el proveedor descarga; admite hasta 4096 caracteres. Si empieza con http, en mayúsculas o minúsculas, tiene que ser una URL http:// o https://; si no, la respuesta es 422. Consulta la sección «Enviar un archivo por URL».
^(?:[Hh][Tt][Tt][Pp][Ss]?:\/\/.+|(?!\.\.?$)(?![Hh][Tt][Tt][Pp])[^\/\\\x00]+)$length <= 4096file_type?|Tipo del archivo. Es obligatorio cuando file es una URL que no termina en una extensión reconocida; si lo envías, manda sobre la extensión. Valores: image, video, audio, document, sticker. Consulta la sección «Enviar un archivo por URL».
"image""video""audio""document""sticker"nullfile_name?|Nombre con el que el contacto ve un documento, con su extensión (por ejemplo factura.pdf), de hasta 255 caracteres. Es obligatorio al enviar un documento por URL que no termina en una extensión de la lista. Consulta la sección «Enviar un archivo por URL».
1 <= length <= 255temporal_uuid?stringnote?booleanoptions?array<>1 <= items <= 10reference_message_uid?|length <= 255client_reference?|^[\x21-\x7E]{1,64}$length <= 64link_preview?booleanlink_preview_meta?OK
application/json- response
message*|nullcurl -X POST "https://example.com/contacts/0/message" \ -H "Content-Type: application/json" \ -d '{ "device_id": 0, "contact_id": 0 }'{ "message": { "id": 0, "contact_id": 0, "device_id": 0, "direction": "outbound", "type": "text", "status": "sent", "error": { "code": 0, "title": "string", "reason": "string" }, "client_reference": "string", "created_at": "string", "updated_at": "string" }}Autenticación y permisos
El formato de la clave, los permisos por dominio, qué está abierto a claves y los errores que devuelven el portero y la entrada de la API.
Estados de mensaje
Saber qué pasó con cada mensaje que mandaste —en cola, enviado, entregado, leído o falló—, por el aviso message.status_changed o consultando el mensaje.