Skip to main content
Version: v0.23.1

Manage WhatsApp groups

Create groups, read their metadata and participant lists, add or remove members, change admins, edit the subject and description, share invite links, and leave — all through one session's REST API.

Prerequisites
  • A session that is started and connected. See Sessions and Connect your first session.
  • An API key with the operator role for any write (create, modify, leave, revoke). Read routes accept any valid key. See Authentication.
  • A group id in WhatsApp's @g.us format, for example 120363021234567890@g.us.

Examples use the local base URL http://localhost:2785/api, a session id of 8f3c2b1a-9d4e-4c7a-8b2f-1e6d5a4c3b2a, and the header X-API-Key: YOUR_API_KEY. Replace YOUR_API_KEY with a key from your dashboard. In production, swap the base URL for your domain over TLS.

How group routes work

Every group route is nested under a session, so the session id is always in the path. There are 22 in all — listing and reading, joining, participant management, membership requests, subject and description, the group picture, group settings, invite links, and leaving:

GET /api/sessions/{sessionId}/groups
POST /api/sessions/{sessionId}/groups
GET /api/sessions/{sessionId}/groups/{groupId}
POST /api/sessions/{sessionId}/groups/{groupId}/participants
DELETE /api/sessions/{sessionId}/groups/{groupId}/participants
POST /api/sessions/{sessionId}/groups/{groupId}/participants/promote
POST /api/sessions/{sessionId}/groups/{groupId}/participants/demote
PUT /api/sessions/{sessionId}/groups/{groupId}/subject
PUT /api/sessions/{sessionId}/groups/{groupId}/description
GET /api/sessions/{sessionId}/groups/{groupId}/invite-code
POST /api/sessions/{sessionId}/groups/{groupId}/invite-code/revoke
POST /api/sessions/{sessionId}/groups/{groupId}/leave
GET /api/sessions/{sessionId}/groups/{groupId}/membership-requests
POST /api/sessions/{sessionId}/groups/{groupId}/membership-requests/approve
POST /api/sessions/{sessionId}/groups/{groupId}/membership-requests/reject
POST /api/sessions/{sessionId}/groups/join
GET /api/sessions/{sessionId}/groups/join-info
GET /api/sessions/{sessionId}/groups/{groupId}/picture
PUT /api/sessions/{sessionId}/groups/{groupId}/picture
DELETE /api/sessions/{sessionId}/groups/{groupId}/picture
GET /api/sessions/{sessionId}/groups/{groupId}/settings
PUT /api/sessions/{sessionId}/groups/{groupId}/settings

The seven at the end of that list — joining by invite code, previewing an invite before joining, the group picture, and the group settings — are covered per-field in the API reference rather than on this page.

Two rules apply to all of them:

  • Identifiers. A group is addressed by its @g.us jid; an individual member by their @c.us jid (the phone number in international format without +, for example 628123456789@c.us).
  • Roles. GET routes need a plain API key. Every non-GET route — create, join, participant changes, subject, description, the group picture, group settings, membership-request approve and reject, leave, and revoke — requires an operator-role key and returns 403 otherwise.

The session must be started. Any group route on a session that is not started returns 400.

List groups

GET /api/sessions/{sessionId}/groups returns the groups the session belongs to as a raw array (no envelope). Page with the limit (1–1000, default 1000) and offset (default 0) query parameters.

curl "http://localhost:2785/api/sessions/8f3c2b1a-9d4e-4c7a-8b2f-1e6d5a4c3b2a/groups?limit=50&offset=0" \
-H "X-API-Key: YOUR_API_KEY"
[
{
"id": "120363021234567890@g.us",
"name": "Project Team",
"participantsCount": 12,
"isAdmin": true,
"linkedParentJID": null
}
]

isAdmin reflects whether this session's account is an admin of the group — check it before attempting a write that requires group-admin rights on WhatsApp's side. linkedParentJID is set when the group is linked to a community, otherwise null.

Read group metadata

GET /api/sessions/{sessionId}/groups/{groupId} returns full details, including the participant list. It returns 404 if the group is not found in this session.

curl "http://localhost:2785/api/sessions/8f3c2b1a-9d4e-4c7a-8b2f-1e6d5a4c3b2a/groups/120363021234567890@g.us" \
-H "X-API-Key: YOUR_API_KEY"
{
"id": "120363021234567890@g.us",
"name": "Project Team",
"description": "Internal coordination group.",
"owner": "628123456789@c.us",
"createdAt": 1718900000,
"isReadOnly": false,
"isAnnounce": false,
"linkedParentJID": null,
"participants": [
{
"id": "628123456789@c.us",
"number": "628123456789",
"name": "Alice",
"isAdmin": true,
"isSuperAdmin": true
}
]
}

Each participant carries isAdmin (group admin) and isSuperAdmin (the group owner). isAnnounce is true when only admins can post: the group setting. isReadOnly answers for the calling account (since v0.22.0): true means this account cannot post. An admin of an announce-only group therefore reads false there, and the two fields no longer always agree.

Create a group

POST /api/sessions/{sessionId}/groups creates a group from a name and a non-empty list of participant jids. The session's own account becomes the owner. Both fields are required; an empty participants array fails validation with 400, as does one over 256 entries. The name is capped at 100 characters.

curl -X POST "http://localhost:2785/api/sessions/8f3c2b1a-9d4e-4c7a-8b2f-1e6d5a4c3b2a/groups" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Project Team",
"participants": ["628123456789@c.us", "628987654321@c.us"]
}'

The response is the created group (status 201). Use the returned id as the groupId in every later call.

{
"id": "120363021234567890@g.us",
"name": "Project Team",
"participantsCount": 3,
"isAdmin": true,
"linkedParentJID": null
}
Create a group on the Baileys engine — whatsapp-web.js answers 501

Since v0.16.0 this route refuses outright on the whatsapp-web.js engine, which is the default (ENGINE_TYPE unset). The library still declares createGroup, but its page code reaches a WhatsApp Web internal that no longer exists, so every call already failed — as an opaque 500 rather than a stated refusal. Bare and @c.us-qualified participant ids fail identically.

Set ENGINE_TYPE=baileys for the session that creates groups. The route, its body, and its Baileys behaviour are unchanged. Every other route on this page works on both engines.

Manage participants

The four participant routes all take the same body — a non-empty participants array of @c.us jids, at most 256 per call (more returns 400).

{ "participants": ["628123456789@c.us"] }

The response carries the acknowledgement message plus a results array with one entry per requested participant:

ActionMethod + pathAcknowledgement message
AddPOST .../participantsParticipants added
RemoveDELETE .../participantsParticipants removed
Promote to adminPOST .../participants/promoteParticipants promoted to admin
Demote from adminPOST .../participants/demoteParticipants demoted from admin

Each results entry reports success, the engine-reported status code, and a message when the engine gave one. A partial refusal does not fail the request — the response stays 200 and the refused entries say why. On whatsapp-web.js an entry can fail with, for example, 403 (invite-only, or the account is not a group admin), 404 (not registered on WhatsApp), or 409 (already a member). Only a total refusal — every participant refused, or the operation itself rejected — returns 403.

{
"success": true,
"message": "Participants added",
"results": [
{ "id": "628123456789@c.us", "success": true, "status": 200 },
{ "id": "628999888777@c.us", "success": false, "status": 404 }
]
}

Two whatsapp-web.js specifics:

  • On an add, a 403 entry can still be a success. When the account can't add someone directly, whatsapp-web.js delivers a private group invite instead; the entry reports success: true, status: 403, and an invite-sent message — so an all-invite batch resolves rather than reading as a total refusal.
  • On remove, promote, and demote, whatsapp-web.js confirms only the batch as a whole. Each entry carries the batch status with a message saying so (confirmed with the batch — wwebjs reports no per-participant outcome) — not an individually confirmed outcome.

Add participants

curl -X POST "http://localhost:2785/api/sessions/8f3c2b1a-9d4e-4c7a-8b2f-1e6d5a4c3b2a/groups/120363021234567890@g.us/participants" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "participants": ["628123456789@c.us"] }'
{
"success": true,
"message": "Participants added",
"results": [{ "id": "628123456789@c.us", "success": true, "status": 200 }]
}

Remove participants

Removal is a DELETE that carries a JSON body — set the Content-Type header so the body is parsed.

curl -X DELETE "http://localhost:2785/api/sessions/8f3c2b1a-9d4e-4c7a-8b2f-1e6d5a4c3b2a/groups/120363021234567890@g.us/participants" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "participants": ["628123456789@c.us"] }'
{
"success": true,
"message": "Participants removed",
"results": [{ "id": "628123456789@c.us", "success": true, "status": 200 }]
}

Promote and demote admins

promote grants group-admin rights; demote revokes them. The session's account must itself be a group admin for either to take effect.

curl -X POST "http://localhost:2785/api/sessions/8f3c2b1a-9d4e-4c7a-8b2f-1e6d5a4c3b2a/groups/120363021234567890@g.us/participants/promote" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "participants": ["628123456789@c.us"] }'
{
"success": true,
"message": "Participants promoted to admin",
"results": [{ "id": "628123456789@c.us", "success": true, "status": 200 }]
}

Change the subject and description

The subject is the group name. PUT .../subject requires a non-empty subject (capped at 100 characters).

curl -X PUT "http://localhost:2785/api/sessions/8f3c2b1a-9d4e-4c7a-8b2f-1e6d5a4c3b2a/groups/120363021234567890@g.us/subject" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "subject": "New Team Name" }'
{ "success": true, "message": "Group subject updated" }

PUT .../description requires a description field (capped at 1024 characters). Unlike the subject, an empty string is valid and clears the description.

curl -X PUT "http://localhost:2785/api/sessions/8f3c2b1a-9d4e-4c7a-8b2f-1e6d5a4c3b2a/groups/120363021234567890@g.us/description" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "description": "Internal coordination group." }'
{ "success": true, "message": "Group description updated" }

GET .../invite-code returns the current invite code and the full https://chat.whatsapp.com/<code> link.

curl "http://localhost:2785/api/sessions/8f3c2b1a-9d4e-4c7a-8b2f-1e6d5a4c3b2a/groups/120363021234567890@g.us/invite-code" \
-H "X-API-Key: YOUR_API_KEY"
{
"inviteCode": "AbCdEf123456",
"inviteLink": "https://chat.whatsapp.com/AbCdEf123456"
}

To invalidate the existing link and mint a fresh one, call POST .../invite-code/revoke with an empty body. The response contains the new code and link — anyone holding the old link can no longer join.

curl -X POST "http://localhost:2785/api/sessions/8f3c2b1a-9d4e-4c7a-8b2f-1e6d5a4c3b2a/groups/120363021234567890@g.us/invite-code/revoke" \
-H "X-API-Key: YOUR_API_KEY"
{
"inviteCode": "XyZ987654321",
"inviteLink": "https://chat.whatsapp.com/XyZ987654321",
"message": "Invite code revoked and new one generated"
}

Leave a group

POST .../leave removes the session's own account from the group. Send an empty body.

curl -X POST "http://localhost:2785/api/sessions/8f3c2b1a-9d4e-4c7a-8b2f-1e6d5a4c3b2a/groups/120363021234567890@g.us/leave" \
-H "X-API-Key: YOUR_API_KEY"
{ "success": true, "message": "Left the group" }

Membership requests (join approval)

When a group has the admin-approval setting enabled, users who tap the invite link do not join immediately — they land in a pending queue. Since v0.15.0 you can list, approve, and reject those requests over the API, and receive a group.join_request webhook event when someone asks to join.

All three routes require the session's WhatsApp account to be a group admin.

List pending requests

curl "http://localhost:2785/api/sessions/8f3c2b1a-9d4e-4c7a-8b2f-1e6d5a4c3b2a/groups/120363021234567890@g.us/membership-requests" \
-H "X-API-Key: YOUR_API_KEY"
[
{
"participantId": "628123456789@c.us",
"addedById": "628987654321@c.us",
"method": "invite_link",
"requestedAt": 1754700000
}
]

An empty array means no one is waiting.

FieldTypeMeaning
participantIdstringNeutral id of the user asking to join. Always present — this is the value you pass back to approve or reject.
addedByIdstringWho created the request. Differs from the requester on a non-admin add.
methodstringHow the request was made: invite_link, non_admin_add, or linked_group_join.
requestedAtnumberUnix seconds (not milliseconds, not an ISO string) the request was created.

Only participantId is guaranteed. Fields the engine does not report are omitted rather than defaulted, so read them defensively.

Approve requests

curl -X POST "http://localhost:2785/api/sessions/8f3c2b1a-9d4e-4c7a-8b2f-1e6d5a4c3b2a/groups/120363021234567890@g.us/membership-requests/approve" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "participants": ["628123456789@c.us"] }'

Reject requests

curl -X POST "http://localhost:2785/api/sessions/8f3c2b1a-9d4e-4c7a-8b2f-1e6d5a4c3b2a/groups/120363021234567890@g.us/membership-requests/reject" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "participants": ["628123456789@c.us"] }'

Both approve and reject take the same participants body as the other participant routes and return a results array with per-participant outcomes. A partial refusal still answers 200 — read results for the per-entry outcome rather than relying on the status code.

An omitted participants acts on the whole queue

Unlike the other participant routes, these two accept a body that names nobody — and that means every pending request. Approving an empty queue is a harmless no-op returning an empty results, but sending {} against a queue of forty approves all forty.

Go callers need one extra care: a nil slice means "every request", while an empty slice []string{} is a 400. Before v0.16.0 omitempty dropped the empty slice and it silently meant "every request" too.

Webhook event

When someone requests to join a group the session administers, a group.join_request event fires on every active webhook that subscribes to it. See Webhooks — event catalog for the payload shape.

Do it with the SDK

The JavaScript SDK wraps every route above. Note the client baseUrl is the host root (http://localhost:2785), without the /api prefix.

import { OpenWAClient } from '@rmyndharis/openwa';

const client = new OpenWAClient({
baseUrl: 'http://localhost:2785',
apiKey: 'YOUR_API_KEY',
});

const group = await client.groups.create('8f3c2b1a-9d4e-4c7a-8b2f-1e6d5a4c3b2a', {
name: 'Project Team',
participants: ['628123456789@c.us', '628987654321@c.us'],
});

await client.groups.addParticipants('8f3c2b1a-9d4e-4c7a-8b2f-1e6d5a4c3b2a', group.id, ['628111222333@c.us']);
await client.groups.promoteParticipants('8f3c2b1a-9d4e-4c7a-8b2f-1e6d5a4c3b2a', group.id, ['628111222333@c.us']);
await client.groups.setSubject('8f3c2b1a-9d4e-4c7a-8b2f-1e6d5a4c3b2a', group.id, 'Project Team — Q3');

const { inviteLink } = await client.groups.inviteCode('8f3c2b1a-9d4e-4c7a-8b2f-1e6d5a4c3b2a', group.id);
console.log(inviteLink);

See SDK usage for client setup and typed error handling.

Troubleshooting

StatusCauseFix
400Validation failed (empty name, empty or over-256 participants, empty subject, missing description, or name/subject over 100 / description over 1024 characters), or the session is not started.Send a complete body within the length limits; start the session first (see Sessions).
401Missing or invalid X-API-Key.Send a valid key in the X-API-Key header.
403The key's role is below operator on a write route — or a participant write was refused for every requested participant (a total refusal), for example because the session's account is not a group admin.Use an operator-role key (see Authentication); for a total refusal, read the per-participant detail in the error message and confirm the account's group-admin rights.
404The group jid is not found in this session.Confirm the @g.us id, and that this session is a member.
Group-admin rights are separate from API roles

An operator-role API key authorizes the request, but participant, subject, description, and invite changes still require the session's WhatsApp account to be a group admin. Check isAdmin on the group before attempting them.

Next steps