Ir al contenido

Grupos y comunidades

Con estas rutas tu app maneja los grupos del número vinculado como lo haría un administrador desde el teléfono: crea grupos, agrega y quita participantes, cambia roles, pide y revoca el enlace de invitación, aprueba pedidos para entrar, cambia el nombre, la descripción, la foto y los ajustes, y arma comunidades con sus grupos.

Las consultas responden en el momento. Los cambios son comandos durables: el motor los guarda, responde 202 y los ejecuta contra WhatsApp; el resultado llega en el comando y en el ledger. Lo explica Comandos asíncronos y eventos.

Cada grupo se identifica por su peer_ref en la instancia (el grupo.peer_ref de la lista o de un evento). Es el {grupo_ref} de las rutas. El mismo grupo visto desde otro número es otra fila, con otro peer_ref.

  • Una instancia conectado. Las consultas a WhatsApp (actualizar=true, invitaciones, pedidos para entrar) 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 grupos y comunidades GET /v1/instances/{id}/groups groups:read
Ver un grupo y sus participantes GET /v1/instances/{id}/groups/{grupo_ref} groups:read
Consultar una invitación sin entrar POST /v1/instances/{id}/groups/invite/info groups:read
Ver los pedidos para entrar GET /v1/instances/{id}/groups/{grupo_ref}/requests groups:read
Crear un grupo POST /v1/instances/{id}/groups groups:write
Agregar, quitar, promover o degradar POST …/groups/{grupo_ref}/participants groups:write
Obtener o revocar el enlace POST …/groups/{grupo_ref}/invite-link groups:write
Entrar con un enlace POST /v1/instances/{id}/groups/join groups:write
Aprobar o rechazar pedidos POST …/groups/{grupo_ref}/requests groups:write
Nombre, descripción, foto y ajustes PUT …/groups/{grupo_ref}/name, /description, /picture, /settings; DELETE …/picture groups:write
Salir del grupo POST …/groups/{grupo_ref}/leave groups:write
Mandar la invitación por mensaje POST …/groups/{grupo_ref}/invite/send groups:read y messages:send
Crear una comunidad POST /v1/instances/{id}/communities groups:write
Ver una comunidad y sus grupos GET /v1/instances/{id}/communities/{comunidad_ref} groups:read
Participantes de la comunidad GET …/communities/{comunidad_ref}/participants groups:read
Vincular o desvincular un grupo POST …/communities/{comunidad_ref}/links groups:write

Para recibir los eventos, además, events:read (ledger o stream) o una suscripción de webhooks.

Todos los comandos piden una Idempotency-Key (de 1 a 200 caracteres): repetir el pedido con la misma clave devuelve el mismo comando y nunca crea dos grupos.

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

GET /v1/instances/{id}/groups devuelve lo que el motor sabe de cada grupo, de a limit (1 a 200; por omisión, 50), con next_cursor y has_more. Con actualizar=true el motor pide antes la lista a WhatsApp: los grupos de los que el número ya no es parte quedan con miembro: false. Con comunidades=true devuelve solo las comunidades.

GET …/groups/{grupo_ref} trae la ficha del grupo y sus participantes, cada uno con su rol (miembro, admin, superadmin) y su telefono cuando el motor tiene comprobado su número (un LID nunca se convierte en teléfono).

Ventana de terminal
curl "https://motor.ejemplo.com/v1/instances/INSTANCIA_ID/groups?actualizar=true&limit=50" \
-H "Authorization: Bearer TU_API_KEY"
# Un grupo y sus participantes:
curl https://motor.ejemplo.com/v1/instances/INSTANCIA_ID/groups/GRUPO_REF \
-H "Authorization: Bearer TU_API_KEY"
GET …/groups/GRUPO_REF · 200 OK
{
"grupo": {
"grupo": { "peer_ref": "4b7d9e01-0000-4000-8000-0000000000b1", "jid": "GRUPO@g.us", "clase": "grupo", "aliases": [] },
"nombre": "Ventas zona norte",
"descripcion": "Coordinación diaria",
"anuncio": false,
"bloqueado": true,
"aprobacion": false,
"modo_agregar": "admin_add",
"efimero_seg": 0,
"es_comunidad": false,
"participantes": 3,
"miembro": true,
"rol_propio": "superadmin",
"suspendido": false,
"version": 7,
"actualizado_en": "2026-10-24T15:30:00Z"
},
"participantes": [
{
"participante": { "peer_ref": "…", "jid": "CONTACTO@lid", "clase": "usuario", "aliases": ["CONTACTO@s.whatsapp.net"] },
"telefono": "+NUMERO_DEL_CONTACTO",
"rol": "admin",
"cambio_en": "2026-10-24T15:29:10Z",
"desde": "motor"
}
]
}

POST /v1/instances/{id}/groups crea el grupo con el número como superadmin. Solo nombre es obligatorio (de 1 a 25 caracteres, el límite de WhatsApp).

Campo Qué es
nombre De 1 a 25 caracteres.
participantes Hasta 256 destinos (telefono, jid o peer_ref, uno por destino). El número entra solo.
anuncio Solo los administradores escriben.
bloqueado Solo los administradores editan la información.
aprobacion Entrar exige la aprobación de un administrador.
modo_agregar admin_add o all_member_add (de fábrica, todos agregan).
efimero_seg Mensajes temporales: 0, 86400, 604800 o 7776000.
comunidad jid o peer_ref de una comunidad del número: el grupo nace dentro de ella.
Ventana de terminal
curl -X POST https://motor.ejemplo.com/v1/instances/INSTANCIA_ID/groups \
-H "Authorization: Bearer TU_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: grupo-ventas-norte" \
-d '{
"nombre": "Ventas zona norte",
"participantes": [{"telefono": "+NUMERO_DEL_CONTACTO"}],
"bloqueado": true,
"modo_agregar": "admin_add"
}'
202 Accepted
{
"command_id": "5c2e7a10-0000-4000-8000-0000000000c3",
"instance_id": "8a1d2c3e-0000-4000-8000-0000000000a1",
"idempotency_key": "grupo-ventas-norte",
"type": "group.create",
"generation": 1,
"status": "aceptado",
"created_at": "2026-10-24T15:28:00Z",
"updated_at": "2026-10-24T15:28:00Z"
}

Cuando termina, GET /v1/commands/{id} trae status: completado y, en result, el JID del grupo y el resultado de cada participante tal como lo dio WhatsApp, el propio número incluido. codigo 0 es que entró; un 403 es que su privacidad no deja agregarlo y viene una invitacion con su vencimiento; un 409, que ya estaba.

GET /v1/commands/{id} · result
{
"grupo": { "jid": "GRUPO@g.us" },
"participantes": [
{ "jid": "TU_NUMERO@s.whatsapp.net", "codigo": 0, "rol": "superadmin" },
{ "jid": "CONTACTO@s.whatsapp.net", "codigo": 0, "rol": "miembro" },
{ "jid": "OTRO_CONTACTO@s.whatsapp.net", "codigo": 403, "rol": "miembro",
"invitacion": "CODIGO_DE_INVITACION", "invitacion_vence": "2026-10-27T15:28:01Z" }
],
"hechos": 2,
"fallidos": 1
}

POST …/groups/{grupo_ref}/participants aplica una accion a hasta 50 participantes: add (agregar), remove (quitar), promote (hacer administrador) o demote (quitarle el rol). El result trae el resultado de cada uno (codigo 0: hecho; si no, el código del servidor), con hechos y fallidos, y el ledger recibe un group.participants con lo que cambió.

Ventana de terminal
curl -X POST https://motor.ejemplo.com/v1/instances/INSTANCIA_ID/groups/GRUPO_REF/participants \
-H "Authorization: Bearer TU_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: ventas-norte-promover-1" \
-d '{"accion": "promote", "participantes": [{"peer_ref": "4b7d9e01-0000-4000-8000-0000000000b2"}]}'

El enlace de invitación es un comando: POST …/groups/{grupo_ref}/invite-link (solo un administrador). Su result.enlace trae https://chat.whatsapp.com/…, y el motor guarda el último en la ficha del grupo (invitacion). Con {"revocar": true} el enlace vigente deja de servir y WhatsApp da uno nuevo.

Con un enlace (el tuyo o el de otro número):

  • Consultarlo sin entrar: POST …/groups/invite/info con enlace (el enlace o solo su código) responde en el momento con nombre, descripcion, participantes, aprobacion, es_comunidad y creado_en. Un enlace inválido es 404 invitacion_invalida; revocado o vencido, 410 invitacion_revocada.
  • Entrar: POST …/groups/join con enlace es un comando group.join. Su result trae el JID del grupo y pendiente_aprobacion: true si el grupo exige que un administrador apruebe la entrada. Si el número ya estaba, falla con ya_es_miembro.
  • Mandarlo por mensaje: POST …/groups/{grupo_ref}/invite/send manda el último enlace que obtuvo el motor a hasta 20 destinos, como un texto por destino (con texto delante, hasta 1000 caracteres). No inscribe a nadie: cada uno decide si entra. Responde con el enlace y un comando por destino, con la Idempotency-Key más :0, :1… (por eso esta clave admite hasta 190 caracteres). Si el motor todavía no pidió el enlace, 409 sin_enlace.
Ventana de terminal
# Pide el enlace (comando):
curl -X POST https://motor.ejemplo.com/v1/instances/INSTANCIA_ID/groups/GRUPO_REF/invite-link \
-H "Authorization: Bearer TU_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: ventas-norte-enlace-1" \
-d '{}'
# Consulta un enlace sin entrar:
curl -X POST https://motor.ejemplo.com/v1/instances/INSTANCIA_ID/groups/invite/info \
-H "Authorization: Bearer TU_API_KEY" \
-H "Content-Type: application/json" \
-d '{"enlace": "https://chat.whatsapp.com/CODIGO_DE_INVITACION"}'
# Entra con el enlace (comando):
curl -X POST https://motor.ejemplo.com/v1/instances/INSTANCIA_ID/groups/join \
-H "Authorization: Bearer TU_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: unirse-proveedores" \
-d '{"enlace": "https://chat.whatsapp.com/CODIGO_DE_INVITACION"}'

En un grupo con aprobacion, quien entra con el enlace queda pendiente. GET …/groups/{grupo_ref}/requests consulta los pedidos a WhatsApp en el momento (solo un administrador; si no, 403 sin_permiso): cada uno trae jid, telefono cuando se conoce y momento. POST …/groups/{grupo_ref}/requests los resuelve con accion approve o reject para hasta 50 participantes; el result trae el resultado de cada uno y los que entran llegan en un group.participants.

Ventana de terminal
curl https://motor.ejemplo.com/v1/instances/INSTANCIA_ID/groups/GRUPO_REF/requests \
-H "Authorization: Bearer TU_API_KEY"
curl -X POST https://motor.ejemplo.com/v1/instances/INSTANCIA_ID/groups/GRUPO_REF/requests \
-H "Authorization: Bearer TU_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: ventas-norte-aprobar-1" \
-d '{"accion": "approve", "participantes": [{"jid": "CONTACTO@lid"}]}'

Cada cambio es un comando con su Idempotency-Key:

Cambio Ruta Cuerpo Tipo de comando
Nombre PUT …/groups/{grupo_ref}/name {"nombre": "…"} (1 a 25 caracteres) group.name
Descripción PUT …/groups/{grupo_ref}/description {"texto": "…"} (hasta 2048; vacío la borra) group.description
Foto PUT …/groups/{grupo_ref}/picture {"media_id": "…"} group.picture
Quitar la foto DELETE …/groups/{grupo_ref}/picture sin cuerpo group.picture
Un ajuste PUT …/groups/{grupo_ref}/settings {"ajuste": "…", "valor": true} group.setting
Salir POST …/groups/{grupo_ref}/leave sin cuerpo group.leave

La foto es un JPEG que subiste antes a esta instancia con POST /v1/instances/{id}/media/uploads (ver Multimedia), ya listo, de hasta 2 MB y de 192 a 2048 píxeles por lado: el motor la recorta al centro y la deja en 640 × 640, la medida que acepta WhatsApp.

ajuste es uno por comando:

ajuste Qué hace
announce Con valor: true, solo los administradores escriben.
locked Con valor: true, solo los administradores editan la información.
approval Con valor: true, entrar exige la aprobación de un administrador.
member_add Quién agrega participantes: va con modo (admin_add o all_member_add), sin valor.
Ventana de terminal
curl -X PUT https://motor.ejemplo.com/v1/instances/INSTANCIA_ID/groups/GRUPO_REF/settings \
-H "Authorization: Bearer TU_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: ventas-norte-anuncio-on" \
-d '{"ajuste": "announce", "valor": true}'
curl -X PUT https://motor.ejemplo.com/v1/instances/INSTANCIA_ID/groups/GRUPO_REF/name \
-H "Authorization: Bearer TU_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: ventas-norte-nombre-2" \
-d '{"nombre": "Ventas norte 2026"}'

Una comunidad es un grupo con es_comunidad: true que agrupa otros grupos.

  • Crearla: POST /v1/instances/{id}/communities con nombre (1 a 25 caracteres) y, si quieres, descripcion (hasta 2048) y aprobacion. WhatsApp crea solo su grupo de anuncios (subgrupo_por_defecto: true), que el motor lee y deja vinculado. El result trae el JID de la comunidad. Como un grupo, si pudo salir y no hubo respuesta, queda ambiguo.
  • Un grupo nuevo dentro: POST …/groups con comunidad (hay que administrarla).
  • Vincular o desvincular un grupo existente: POST …/communities/{comunidad_ref}/links con grupo (jid o peer_ref) y vincular (true o false). El número tiene que administrar la comunidad. Después el motor lee los grupos de la comunidad y concilia la lista: un vínculo que ya estaba no es un error ni se duplica.
  • Verla: GET …/communities/{comunidad_ref} trae la comunidad y sus subgrupos, cada uno con anuncios, conocido (el número está en ese grupo) y la version de la relación. Con actualizar=true el motor pide la lista a WhatsApp y la concilia. Si ese peer_ref no es una comunidad, 422 no_es_comunidad.
  • Sus participantes: GET …/communities/{comunidad_ref}/participants los consulta a WhatsApp en el momento (jid y telefono); no se guardan.

El {comunidad_ref} es el peer_ref de la comunidad (GET …/groups?comunidades=true).

Ventana de terminal
curl -X POST https://motor.ejemplo.com/v1/instances/INSTANCIA_ID/communities \
-H "Authorization: Bearer TU_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: comunidad-sucursales" \
-d '{"nombre": "Sucursales", "descripcion": "Todas las sucursales"}'
curl -X POST https://motor.ejemplo.com/v1/instances/INSTANCIA_ID/communities/COMUNIDAD_REF/links \
-H "Authorization: Bearer TU_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: sucursales-vincular-norte" \
-d '{"grupo": {"peer_ref": "4b7d9e01-0000-4000-8000-0000000000b1"}, "vincular": true}'
curl "https://motor.ejemplo.com/v1/instances/INSTANCIA_ID/communities/COMUNIDAD_REF?actualizar=true" \
-H "Authorization: Bearer TU_API_KEY"

Todos los cambios nacen aceptado y terminan completado o fallido (o ambiguo, en una creación). Los tipos de comando son group.create, group.leave, group.name, group.description, group.picture, group.invite_link, group.join, group.participants, group.setting, group.requests, community.create y community.link; mandar la invitación es group.invite_send, y cada mensaje que sale de él, un message.text.

El efecto llega al ledger con el command_id del comando y desde: motor. Lo que cambia desde un teléfono llega con desde: whatsapp, y lo que el motor descubre al consultar, con desde: consulta. Cada evento trae el grupo como peer.

Cambió la ficha del grupo. campos trae solo lo que cambió, con su valor nuevo: nombre, descripcion, anuncio, bloqueado, aprobacion, modo_agregar, efimero_seg, suspendido, foto_id, invitacion (solo true: el enlace no viaja en el evento), eliminado, miembro, rol_propio, es_comunidad o comunidad. autor llega cuando WhatsApp dice quién lo hizo.

{
"grupo": { "peer_ref": "…", "jid": "GRUPO@g.us", "clase": "grupo", "aliases": [] },
"campos": { "nombre": "Ventas norte 2026" },
"desde": "motor"
}

Si una consulta con actualizar=true ya no ve un grupo, llega un group.changed con campos: {"miembro": false} y desde: consulta.

Entraron, salieron o cambiaron de rol participantes. Las listas sin movimiento llegan vacías o en null; motivo es el que informa WhatsApp y puede venir vacío. Está en el Catálogo de eventos.

{
"grupo": { "peer_ref": "…", "jid": "GRUPO@g.us", "clase": "grupo", "aliases": [] },
"desde": "motor",
"entran": null,
"salen": null,
"promovidos": [{ "peer_ref": "…", "jid": "CONTACTO@lid", "clase": "usuario", "aliases": [] }],
"degradados": null,
"motivo": ""
}

El número entró a un grupo: lo creó, lo agregaron o entró con un enlace. tipo y motivo son los que informa WhatsApp (tipo: "new" cuando el número lo creó); participantes es la cantidad y autor llega si WhatsApp dice quién lo agregó. Una creación deja un solo group.joined.

{
"grupo": { "peer_ref": "…", "jid": "GRUPO@g.us", "clase": "grupo", "aliases": [] },
"nombre": "Ventas zona norte",
"tipo": "new",
"motivo": "",
"participantes": 2,
"es_comunidad": false,
"desde": "motor"
}

Un grupo se vinculó o se desvinculó de una comunidad. anuncios dice si es su grupo de anuncios y version es la versión de la relación. Cuando un grupo pasa a otra comunidad, la salida de la anterior llega con motivo: "otra_comunidad".

{
"comunidad": { "peer_ref": "…", "jid": "COMUNIDAD@g.us", "clase": "grupo", "aliases": [] },
"grupo": { "peer_ref": "…", "jid": "GRUPO@g.us", "clase": "grupo", "aliases": [] },
"nombre": "Ventas zona norte",
"vinculado": true,
"anuncios": false,
"version": 2,
"desde": "motor"
}

Los mensajes de un grupo no son eventos de este dominio: llegan como message.received con es_grupo: true y autor (ver Mensajes).

Las consultas responden con un error HTTP; el error_code va en type como urn:motor-whatsapp:error:<código> cuando el error es tipado. Los comandos, en cambio, se aceptan con 202 y fallan después con error_code en el comando y en command.failed.

HTTP error_code Cuándo
403 — A tu key le falta el scope de la operación.
403 sin_permiso Ver los pedidos para entrar sin ser administrador.
404 — La instancia no conoce ese grupo: actualiza la lista con actualizar=true.
404 invitacion_invalida El enlace no es válido.
409 no_conectada Una consulta a WhatsApp con la instancia sin conexión.
409 sin_vincular La instancia no está vinculada a un número.
409 sin_enlace Mandar la invitación antes de pedir el enlace.
409 — La misma Idempotency-Key con otros datos.
410 invitacion_revocada El enlace fue revocado o venció.
422 — Un cuerpo inválido: nombre de más de 25 caracteres, member_add sin modo, un efimero_seg que no es 0, 86400, 604800 ni 7776000, el grupo sin jid ni peer_ref.
422 foto_invalida La foto no es un JPEG listo de esta instancia, pesa más de 2 MB o no mide de 192 a 2048 píxeles por lado.
422 no_es_comunidad Ese peer_ref no es una comunidad.

Los error_code de un comando que falla:

error_code Qué pasó
sin_permiso El número no es administrador. WhatsApp responde con un 401 del servidor (server_code: 401, server_origin: iq).
no_es_miembro El número no está en ese grupo.
grupo_no_encontrado WhatsApp dice que el grupo no existe.
invitacion_invalida, invitacion_revocada Entrar con un enlace inválido, revocado o vencido.
ya_es_miembro Entrar con un enlace a un grupo en el que el número ya está.
foto_invalida La foto no se pudo usar.
rechazado_por_whatsapp WhatsApp rechazó el pedido; server_code trae su código.
resultado_incierto Una creación sin respuesta: el comando queda ambiguo.
limitado_por_whatsapp WhatsApp está limitando la operación; el motor reintenta y, si sigue, falla.
  • Vincular un grupo a una comunidad puede quedar limitado por WhatsApp un par de horas (caso A63, 17/10/2026): el motor reintentó tres veces con limitado_por_whatsapp y el teléfono tampoco dejaba. Está en Limitaciones de WhatsApp. La mudanza completa de un grupo entre dos comunidades no se pudo probar; la salida de la comunidad anterior sí llega como community.changed con vinculado: false.
  • El rol lo pone WhatsApp (caso A57, 21/10/2026): sin ser administrador, cambiar el nombre de un grupo con bloqueado falla con sin_permiso (el servidor responde 401); hecho administrador desde el teléfono, el mismo cambio pasa.
  • La privacidad de cada contacto manda (caso A56, 21/10/2026): un contacto con «Quién puede agregarme a grupos» en «Mis contactos» vuelve con codigo: 403 y un código de invitacion que vence a los 3 días. Mándale el enlace con …/invite/send.
  • Invitaciones y aprobación (caso A58, 21/10/2026): el enlace revocado da 410; un pedido para entrar aparece en …/requests con el jid como @lid, y aprobarlo desde el motor deja un group.participants desde: motor.
  • Crear con la red cortada (caso A62, 16/10/2026): el comando quedó ambiguo (resultado_incierto) a los 60 s y, al volver la red, no se mandó otra vez: no apareció ningún grupo duplicado.
  • Comunidades (caso A59, 21/10/2026): crear una comunidad, con su grupo de anuncios y la descripción, y un grupo dentro, pasa. La forma del pedido a WhatsApp es un ajuste del operador (grupos.forma_comunidad; de fábrica, minima) y queda fija al aceptar el comando.
  • Desvincular desde el teléfono (caso A60, 17/10/2026): llega community.changed con vinculado: false y desde: whatsapp, y la lista de la comunidad se concilia sin duplicados.