Skip to content

Groups overview

Messages received in a WhatsApp group carry the group’s id and, when it is known, its name, and sending to a group uses the public identifier grp_{hex32}.

Concepts

Public grp_ identifier

Every public reference to a group uses the format grp_ followed by 32 hexadecimal characters, like the other public prefixes (msg_, ct_, req_, evt_).

The WhatsApp identifier (120363xxxxx@g.us) is never exposed in responses, webhooks or polling: only the Turbo Notify grp_{hex32}.

The to field: three forms

The kind of recipient is set by the key present in to, with no kind or type field:

  • 1:1: to: { "number": "+55..." }, the recipient is a person.
  • Group: to: { "group_id": "grp_..." }, the recipient is a group.
  • By contact: to: { "contact_id": "ct_..." }, the phone comes from the contact registered in Turbo Notify.

The same rule applies to webhook and polling payloads: when to.group_id is present, the recipient is the group.

The from field is always a person

Whoever writes in a group is always a participant, never the group itself. So:

  • Received message: from is the participant who sent the message in the group.
  • Sent message: from is the Turbo Notify number that sent the message.

from never carries a group_id.

Your number in the conversation (number_id and number_alias in the envelope)

The Turbo Notify number taking part in the conversation (1:1 or group, received or sent) is identified by the number_alias field in the envelope of the event ("main" for the main number or the alias of an extra number). To get the details of that number, use GET /v1/numbers/{ref} with the envelope’s number_id (the alias is the one the number had when the event happened).

// Group inbound (envelope + data)
{
"number_alias": "main",
"number_id": "num_0a1b2c3d4e5f60718293a4b5c6d7e8f9",
"data": {
"from": { "contact_id": "ct_...", "name": "Ana Souza", "number": "5511988887777" },
"to": { "group_id": "grp_e442553a56b14e2695f6acfd7d651b1a", "name": "TNT" }
}
}
// Outbound to a group (envelope + data)
{
"number_alias": "main",
"number_id": "num_0a1b2c3d4e5f60718293a4b5c6d7e8f9",
"data": {
"from": { "name": "Turbo Notify", "alias": "main", "number": "5511973117056" },
"to": { "group_id": "grp_e442553a56b14e2695f6acfd7d651b1a", "name": "TNT" }
}
}

To detect group messages, check whether to.group_id is present:

const isGroup = "group_id" in data.to;

Receiving group messages

Each message received in a group is delivered as message.received on the webhook and on polling. from is the participant who sent it; to carries the group_id and the group name at that moment; your number taking part in the group is identified by the number_alias in the envelope.

Sending to a group

To send to a group from the main number, use POST /v1/messages. For extra numbers, use POST /v1/numbers/{ref}/messages where {ref} is the alias of the extra number (or its id, num_…). The to field takes the form { "group_id": "grp_..." }.

Terminal window
curl -X POST "https://api.turbonotify.com/v1/messages" \
-H "Authorization: Bearer YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": { "group_id": "grp_e442553a56b14e2695f6acfd7d651b1a" },
"content": { "text": "Good morning, team" }
}'

Turbo Notify finds the WhatsApp group that matches the grp_{hex32} and sends the message through the number given in {ref}. For 1:1, the to field is an object { "number": "+5511988887777" }.

Querying groups

GET /v1/numbers/{ref}/groups lists the groups seen by a number, with cursor pagination. GET /v1/numbers/{ref}/groups/{group_id} returns the detail of a specific group. Both work on any plan and do not require the groups:messages feature.

Required scope: groups:read.

GET /v1/numbers/{ref}/groups?limit=20&cursor=<opaque>
GET /v1/numbers/{ref}/groups/{group_id}
Parameter (list)TypeDescription
cursorstringOpaque token returned in cursor by the previous page
limitint1 to 200 (default 50)
namestringPartial match on the group name
include_leftboolIncludes groups the number has already left (default false)

Each item, both in the list (data[]) and in the detail, has this format:

{
"id": "grp_9f6f0b37c98043e5835822eeae550e77",
"number_alias": "main",
"name": "Group A",
"description": "Support team",
"participants_count": 12,
"is_announce_only": false,
"is_locked": false,
"ephemeral_seconds": null,
"is_active": true,
"picture": { "kind": "unavailable", "reason": "peer_has_none" },
"created_at": "2026-05-25T10:00:00Z",
"updated_at": "2026-05-25T10:05:00Z"
}

The list wraps the items in { "data": [...], "cursor": "...", "has_more": true }. is_active is false when the number has already left the group (it only shows up in the list with include_left=true). picture has the same format used by the picture’s own endpoint: see Group picture.

GET /v1/numbers/{ref}/groups/{group_id} returns 400 invalid_group_id when the group_id does not match ^grp_[0-9a-f]{32}$ (the format is checked before the lookup). A well-formed group_id that does not exist, or that belongs to another number or organization, returns 404 group_not_found.

Quota and rate limit

A send to a group counts as 1 recipient in the daily recipients limit, no matter how many participants the group has. The contact quota (contact.*) is not affected by sends to groups: groups never create contacts.

Group lifecycle

Turbo Notify sends 10 dedicated webhooks when the state of a group changes. See group lifecycle events for the full catalog.

Limitations

See Limitations for what the API does not offer (creating groups, managing groups and listing members).