Ir al contenido

Canales

Un canal (newsletter, …@newsletter) es una difusión de una vía: su dueño y sus administradores publican y los seguidores leen, ven y reaccionan. Con estas rutas tu app:

  • lista los canales que el número sigue o administra, y consulta un enlace sin seguirlo;
  • crea un canal, lo sigue, lo deja, lo silencia;
  • publica en un canal propio (por el mismo outbox que los mensajes);
  • consulta las publicaciones con sus vistas y reacciones, las marca como vistas y reacciona.

Cada canal se identifica por su peer_ref en la instancia (canal.peer_ref de la lista o de un evento): es el {canal_ref} de las rutas. Las publicaciones tienen dos identificadores que no se confunden: el server_id, el número que asigna WhatsApp (lo usan las vistas, las reacciones y los contadores), y el provider_id, el ID de mensaje.

  • Una instancia conectado. Las consultas a WhatsApp responden 409 no_conectada sin conexión; un comando sin conexión vuelve a la cola y se reintenta.
  • Una API key con estos scopes:
Operación Ruta Scope
Listar los canales GET /v1/instances/{id}/newsletters newsletters:read
Ver un canal GET /v1/instances/{id}/newsletters/{canal_ref} newsletters:read
Consultar un enlace sin seguirlo POST /v1/instances/{id}/newsletters/invite/info newsletters:read
Las publicaciones GET …/newsletters/{canal_ref}/messages newsletters:read
Traer vistas y reacciones POST …/newsletters/{canal_ref}/updates newsletters:read
Recibir en vivo POST …/newsletters/{canal_ref}/live newsletters:read
Crear un canal POST /v1/instances/{id}/newsletters newsletters:write
Seguir POST /v1/instances/{id}/newsletters/follow newsletters:write
Dejar de seguir POST …/newsletters/{canal_ref}/unfollow newsletters:write
Silenciar o reactivar PUT …/newsletters/{canal_ref}/mute newsletters:write
Marcar como vistas POST …/newsletters/{canal_ref}/view newsletters:write
Reaccionar POST …/newsletters/{canal_ref}/reaction newsletters:write
Publicar POST /v1/instances/{id}/messages messages:send

Los comandos piden una Idempotency-Key (de 1 a 200 caracteres). Para recibir los eventos, además, events:read o una suscripción de webhooks.

Los ejemplos de Go y JavaScript usan la función auxiliar llamar que se define en el primer paso: el SDK de Go no trae métodos de canales, así que es HTTP directo con el token del cliente.

GET /v1/instances/{id}/newsletters devuelve los canales según lo último que dijo WhatsApp, de a limit (1 a 200; por omisión, 50), con next_cursor y has_more. Cada uno trae su rol (owner y admin publican; subscriber lo sigue; guest, no), siguiendo, silenciado, suscriptores, verificado y el enlace en invitacion. Con actualizar=true el motor pide antes la lista a WhatsApp: los que ya no están quedan siguiendo: false.

POST …/newsletters/invite/info con enlace (https://whatsapp.com/channel/… o solo su código) dice qué canal es sin seguirlo: jid, nombre, descripcion, suscriptores, verificado, estado y creado_en. Uno que no existe es 404 canal_no_encontrado.

Ventana de terminal
curl "https://motor.ejemplo.com/v1/instances/INSTANCIA_ID/newsletters?actualizar=true" \
-H "Authorization: Bearer TU_API_KEY"
curl -X POST https://motor.ejemplo.com/v1/instances/INSTANCIA_ID/newsletters/invite/info \
-H "Authorization: Bearer TU_API_KEY" \
-H "Content-Type: application/json" \
-d '{"enlace": "https://whatsapp.com/channel/CODIGO_DEL_CANAL"}'
GET …/newsletters/CANAL_REF · 200 OK
{
"canal": {
"canal": { "peer_ref": "4b7d9e01-0000-4000-8000-0000000000c1", "jid": "CANAL@newsletter", "clase": "canal", "aliases": [] },
"nombre": "Novedades de la tienda",
"descripcion": "Ofertas y horarios",
"invitacion": "https://whatsapp.com/channel/CODIGO_DEL_CANAL",
"rol": "owner",
"siguiendo": true,
"silenciado": false,
"suscriptores": 128,
"verificado": false,
"reacciones": "all",
"en_vivo": false,
"version": 4,
"actualizado_en": "2026-10-24T16:00:00Z"
}
}
Operación Ruta Cuerpo Tipo de comando
Crear POST …/newsletters nombre (1 a 100), descripcion (hasta 2048), foto_media_id newsletter.create
Seguir POST …/newsletters/follow {"canal": {"enlace": "…"}} o {"canal": {"jid": "…"}} newsletter.follow
Dejar de seguir POST …/newsletters/{canal_ref}/unfollow sin cuerpo newsletter.follow
Silenciar o reactivar PUT …/newsletters/{canal_ref}/mute {"silenciar": true} o false newsletter.mute

Al crear, el result del comando trae el JID del canal (canal.jid) y su enlace. La foto es un JPEG subido antes a esta instancia, ya listo, de hasta 2 MB y de 192 a 2048 píxeles por lado (ver Multimedia). Si el proceso cae a mitad o WhatsApp no contesta, la creación queda ambiguo con resultado_incierto: el motor nunca la manda dos veces.

Al seguir, el canal queda siguiendo: true con el rol que diga WhatsApp (subscriber).

Ventana de terminal
curl -X POST https://motor.ejemplo.com/v1/instances/INSTANCIA_ID/newsletters \
-H "Authorization: Bearer TU_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: canal-novedades" \
-d '{"nombre": "Novedades de la tienda", "descripcion": "Ofertas y horarios"}'
curl -X POST https://motor.ejemplo.com/v1/instances/INSTANCIA_ID/newsletters/follow \
-H "Authorization: Bearer TU_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: seguir-canal-proveedor" \
-d '{"canal": {"enlace": "https://whatsapp.com/channel/CODIGO_DEL_CANAL"}}'
curl -X PUT https://motor.ejemplo.com/v1/instances/INSTANCIA_ID/newsletters/CANAL_REF/mute \
-H "Authorization: Bearer TU_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: silenciar-canal-proveedor" \
-d '{"silenciar": true}'

Publicar es un envío como cualquier mensaje: POST /v1/instances/{id}/messages con scope messages:send, destino.jid el JID del canal (o destino.peer_ref, su peer_ref) y la Idempotency-Key obligatoria. Sale por el mismo outbox, con su ritmo, y nace en_cola.

  • Se publica texto, imagen, vídeo o nota de voz (tipo text, image, video o ptt), sin cita ni menciones. Otro tipo es 422 tipo_no_admitido_en_canal.
  • Solo el dueño o un administrador publican: si el número no lo es según lo último que dijo WhatsApp, 403 sin_permiso. Si la instancia no conoce el canal, 404: actualiza la lista.
  • Cuando el servidor lo acepta, el result del comando trae el server_id de la publicación y su provider_message_id, y el message.sent trae server_id.
Ventana de terminal
curl -X POST https://motor.ejemplo.com/v1/instances/INSTANCIA_ID/messages \
-H "Authorization: Bearer TU_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: canal-novedades-oferta-42" \
-d '{
"destino": {"jid": "CANAL@newsletter"},
"tipo": "image",
"medio": {"media_id": "8a1d2c3e-0000-4000-8000-0000000000e1"},
"texto": "Esta semana, envío sin cargo."
}'
GET /v1/commands/{id} · result
{ "server_id": 105, "provider_message_id": "PROVIDER_MESSAGE_ID" }

Los seguidores que son instancias del motor reciben la publicación como message.received en el chat del canal, con el mismo server_id.

4. Consulta las publicaciones, las vistas y las reacciones

Sección titulada «4. Consulta las publicaciones, las vistas y las reacciones»

GET …/newsletters/{canal_ref}/messages devuelve las publicaciones que el motor conoce, de la más nueva a la más vieja, de a limit (1 a 200; por omisión, 50). Cada una trae server_id, provider_id, direccion (entrante o saliente), tipo, momento, vistas, reacciones (emoji → cantidad), reaccion_propia y visto.

  • Con actualizar=true el motor pide antes a WhatsApp cantidad publicaciones (1 a 100; por omisión, 20) y guarda sus contadores; una consulta repetida no duplica nada. Solo entonces cada publicación trae contenido (el tipo y el texto o el pie, como en message.received). El archivo de una publicación consultada no se descarga.
  • Para la página siguiente (más vieja), pasa antes con el next_antes de la respuesta mientras has_more sea true.

POST …/newsletters/{canal_ref}/updates trae solo los contadores que cambiaron: desde un momento (desde) o de las publicaciones con un server_id mayor que despues_de. Responde con recibidas (cuántas informó el servidor), y las que cambiaron dejan un newsletter.updates con desde: consulta.

Ventana de terminal
curl "https://motor.ejemplo.com/v1/instances/INSTANCIA_ID/newsletters/CANAL_REF/messages?actualizar=true&cantidad=20" \
-H "Authorization: Bearer TU_API_KEY"
curl -X POST https://motor.ejemplo.com/v1/instances/INSTANCIA_ID/newsletters/CANAL_REF/updates \
-H "Authorization: Bearer TU_API_KEY" \
-H "Content-Type: application/json" \
-d '{"despues_de": 99}'
GET …/messages · 200 OK
{
"publicaciones": [
{
"server_id": 105,
"provider_id": "PROVIDER_MESSAGE_ID",
"direccion": "entrante",
"tipo": "imagen",
"momento": "2026-10-24T16:05:00Z",
"vistas": 3,
"reacciones": { "👍": 2, "❤️": 1 },
"reaccion_propia": "👍",
"visto": true,
"version": 3,
"conteo_en": "2026-10-24T16:20:00Z",
"contenido": { "tipo": "imagen", "texto": "Esta semana, envío sin cargo." }
}
],
"next_antes": 105,
"has_more": true
}

Las dos operaciones usan el server_id, nunca el ID de mensaje:

  • POST …/newsletters/{canal_ref}/view con server_ids (de 1 a 100): suma al contador de vistas (newsletter.view). No marca nada como leído en los otros dispositivos del número.
  • POST …/newsletters/{canal_ref}/reaction con server_id y emoji (hasta 32 caracteres; vacío quita la reacción): newsletter.reaction. El motor fija el ID de la reacción por comando, así que un reintento manda la misma; el result lo trae en reaccion_id.
Ventana de terminal
curl -X POST https://motor.ejemplo.com/v1/instances/INSTANCIA_ID/newsletters/CANAL_REF/view \
-H "Authorization: Bearer TU_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: canal-proveedor-visto-105" \
-d '{"server_ids": [104, 105]}'
curl -X POST https://motor.ejemplo.com/v1/instances/INSTANCIA_ID/newsletters/CANAL_REF/reaction \
-H "Authorization: Bearer TU_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: canal-proveedor-reaccion-105" \
-d '{"server_id": 105, "emoji": "👍"}'

POST …/newsletters/{canal_ref}/live pide a WhatsApp que mande en vivo las vistas y reacciones del canal por un rato, el que diga el servidor: la respuesta trae en_vivo_hasta, y el canal muestra en_vivo mientras no venza. Hay que volver a pedirlo cuando vence. Lo que llegue entra como newsletter.updates.

Los comandos de control (newsletter.create, newsletter.follow, newsletter.mute, newsletter.view y newsletter.reaction) nacen aceptado y terminan completado o fallido; una creación sin respuesta, ambiguo. Una publicación es un envío (message.text, message.image, message.video o message.ptt) y termina como cualquier mensaje: enviado con message.sent, o fallido.

El efecto llega al ledger con su command_id y desde: motor. Lo que llega de WhatsApp (un cambio hecho desde el teléfono) trae desde: whatsapp y no trae command_id; lo que el motor descubre al consultar, desde: consulta. Cada evento trae el canal como peer, con clase: "canal".

El número empezó a seguir un canal (o lo creó).

{
"canal": { "peer_ref": "…", "jid": "CANAL@newsletter", "clase": "canal", "aliases": [] },
"nombre": "Novedades del proveedor",
"rol": "subscriber",
"desde": "motor"
}

El número dejó de seguirlo. rol es el que informó el aviso (suele venir vacío; en las pruebas llegó guest).

{
"canal": { "peer_ref": "…", "jid": "CANAL@newsletter", "clase": "canal", "aliases": [] },
"rol": "guest",
"desde": "whatsapp"
}

Cambió el silencio del canal.

{
"canal": { "peer_ref": "…", "jid": "CANAL@newsletter", "clase": "canal", "aliases": [] },
"silenciado": true,
"desde": "motor"
}

Cambió la ficha del canal. campos trae solo lo que cambió: nombre, descripcion, estado (active, suspended o geosuspended, lo que dice WhatsApp), verificado, rol, siguiendo o silenciado.

{
"canal": { "peer_ref": "…", "jid": "CANAL@newsletter", "clase": "canal", "aliases": [] },
"campos": { "rol": "subscriber", "siguiendo": true, "silenciado": true },
"desde": "whatsapp"
}

Cambiaron las vistas o las reacciones de una o más publicaciones. Solo trae las que cambiaron, con sus valores completos: las vistas nunca bajan y, entre dos datos, gana el más nuevo.

{
"canal": { "peer_ref": "…", "jid": "CANAL@newsletter", "clase": "canal", "aliases": [] },
"publicaciones": [
{ "server_id": 100, "provider_id": "PROVIDER_MESSAGE_ID", "vistas": 3, "reacciones": { "👍": 1 } },
{ "server_id": 101, "vistas": 1, "reacciones": { "❤️": 1 } }
],
"desde": "consulta"
}

Las publicaciones de un canal que el número sigue llegan como message.received en el chat del canal, con server_id (ver Mensajes).

HTTP error_code Cuándo
403 — A tu key le falta el scope (newsletters:read, newsletters:write o messages:send para publicar).
403 sin_permiso Publicar en un canal que el número no administra.
404 — La instancia no conoce ese canal: actualiza la lista con actualizar=true.
404 canal_no_encontrado WhatsApp dice que el canal (o el enlace) no existe.
405 — Silenciar con POST: la ruta es PUT …/mute.
409 no_conectada Una consulta a WhatsApp con la instancia sin conexión.
409 — La misma Idempotency-Key con otros datos.
422 tipo_no_admitido_en_canal Publicar un tipo que no sea texto, imagen, vídeo o nota de voz, o con cita o menciones.
422 — Un cuerpo inválido: nombre fuera de 1 a 100 caracteres, el canal con más de uno de jid, peer_ref y enlace, server_ids vacío.

Los error_code de un comando que falla:

error_code Qué pasó
canal_no_encontrado WhatsApp dice que el canal no existe.
sin_permiso WhatsApp no deja: el número no tiene el rol.
rechazado_por_whatsapp WhatsApp rechazó el pedido; server_code trae su código.
medio_no_disponible La foto del canal nuevo no se pudo leer: no salió nada.
resultado_incierto Una creación sin respuesta: queda ambiguo.
  • Sin contadores en vivo (caso A66, 21/10/2026 y repetido después): «Recibir en vivo» abre la ventana, pero WhatsApp no empuja las vistas ni las reacciones a un dispositivo vinculado. Los contadores solo se ven al consultarlos, y las vistas pueden salir en 0 aunque las haya. Ver Actualizaciones en vivo de canales.
  • El silencio cambiado desde el teléfono no llega (caso A64, 22/10/2026): silenciar o quitar el silencio en el teléfono no produce ningún evento. Dejar el canal y volver a seguirlo sí (newsletter.left, y luego newsletter.changed y newsletter.joined), y al volver el silencio aparece reflejado. Ver Silencio de un canal desde el teléfono.
  • Al volver a seguir, el silencio no se conserva (caso A64, 21/10/2026): un canal que se dejó y se volvió a seguir quedó silenciado: true, aunque antes no lo estaba.
  • Publicar (caso A65, 21/10/2026): un texto, una imagen con pie y una nota de voz salieron enviado, cada uno con su server_id distinto del provider_message_id, y la instancia que seguía el canal los recibió como message.received con el mismo server_id. WhatsApp no rechazó ningún tipo, pero publicar en canales con un número no oficial es experimental.