API reference

Groups

Read the WhatsApp groups your numbers are in, and post a message to one.

Groups

A group is a WhatsApp group one of your connected numbers belongs to. You can read the list, refresh it from the phone, and post a message into a group.

Everything else about a group happens on the phone. Groups are joined, renamed, left and administered there, so this API has no endpoint that creates, edits or deletes one — it would be describing something it cannot do.

Members are counted, never stored

This is the most important thing to know before you build against it.

A group carries the phone numbers of everyone in it, and almost none of those people ever contacted your workspace or agreed to be on file with us. So when the group list is read, the members are counted and the identities are thrown away. participant_count is a number, and there is no endpoint that returns the people behind it, because there is no table that holds them.

Two consequences worth designing around:

  • Group members never become contacts. Messaging a group does not add 241 people to your audience, and they will not appear in GET /contacts.
  • Groups are excluded from campaigns. A campaign audience is built from contacts with recorded consent. A group has neither.

List your groups

GET /api/v1/groups

Returns the groups your connected numbers belong to, largest first, in pages.

Query Default Notes
search Match on group name, case-insensitive
page 1 Which page to return
per_page 25 Page size, 1–100
{
  "data": [
    {
      "id": "5926b085-8cb7-4e18-9c69-c94c783049b6",
      "name": "Wholesale Traders PK",
      "topic": "Bulk orders and stock updates",
      "participant_count": 241,
      "is_admin": true,
      "is_announce": false,
      "can_send": true,
      "connection": {
        "id": "9f2b1c7e-53a8-4a2e-b8f1-2d6c0a4e77b3",
        "display_name": "Sales line"
      },
      "created_at": "2026-03-04T10:00:00+00:00",
      "synced_at": "2026-07-23T09:44:48+00:00"
    }
  ],
  "links": { "first": "…", "last": "…", "prev": null, "next": null },
  "meta": { "current_page": 1, "per_page": 25, "total": 9 }
}
Field Notes
id The group's UUID here. Use it as {id} when you post a message
name As it is on the phone. null for an unnamed group
participant_count How many people are in it. A count only — see above
is_admin Whether your number is an admin of the group
is_announce Whether the group only lets admins post
can_send Whether you may post — read this rather than deriving it
connection Which of your numbers this group belongs to
created_at When the group was created, per the phone. null if unknown
synced_at When this row was last refreshed from the phone

Read can_send, do not derive it

can_send is false when the group only accepts posts from admins and your number is not one. Work it out yourself from is_announce and is_admin and your code will need changing the day another reason to refuse is added.

Refresh from the phone

POST /api/v1/groups/sync

The stored list is a mirror. Someone adds your number to a group, or removes it, and nothing tells us — call this to re-read it.

It runs across every connected number, because this API deliberately exposes no connection ids. Each number is attempted on its own, so one being offline does not cost you the others.

{
  "data": {
    "numbers": [
      { "number": "Sales line",  "refreshed": true,  "groups": 9, "removed": 1, "error": null },
      { "number": "Orders line", "refreshed": false, "groups": 0, "removed": 0, "error": "connection_not_ready" }
    ],
    "groups": 9
  }
}
Field Notes
numbers[].refreshed Whether that number's list was re-read
numbers[].groups How many groups it reported
numbers[].removed Groups it is no longer in, now deleted here
numbers[].error connection_not_ready when the number is offline, else null
groups Total groups in the workspace after the refresh

The response is 200 even when every number failed. The per-number results say what happened, which is more use than one opaque error — and a number being offline is not a fault in your request.

A group you have left is removed, along with the messages you sent to it. It is not kept as a row that has quietly stopped being true.

Running this twice is the same as running it once, so it needs no Idempotency-Key.

Message a group

POST /api/v1/groups/{id}/messages
{
  "body": "New stock has arrived. Orders open until 6pm."
}
Field Required Notes
body yes Up to 4096 characters. Text only — attachments are not offered

Use the id from GET /groups. Every member of the group receives it.

Response

HTTP/1.1 202 Accepted
{
  "data": {
    "id": "4275ce52-0b1d-4a90-8d3f-19a7c0b2e5f1",
    "group_id": "5926b085-8cb7-4e18-9c69-c94c783049b6",
    "status": "queued",
    "failure_code": null,
    "sent_at": null,
    "failed_at": null,
    "created_at": "2026-07-23T09:48:30+00:00"
  }
}

The status ends at sent

queuedsendingsent, or failed. That is the whole lifecycle, and it stops there on purpose.

WhatsApp acknowledges a group message once, but then returns a delivery and a read receipt for every member who gets it. There is no single "the group received it" or "the group read it" — one person opening the message would otherwise mark it read for five hundred. So no delivered or read state is reported for a group, and none should be inferred.

A 202 means the message was queued and handed to your number. As with a direct send, it is not proof that WhatsApp took it; status is.

One message, whatever the group size

A group send costs one message from the same plan allowance as any other send, whether the group has 6 members or 600.

When it is refused

Both of these are refused before anything is queued, because WhatsApp would otherwise accept the message and drop it without telling anyone:

HTTP/1.1 422 Unprocessable Content

{ "message": "Only admins can post to this group." }
HTTP/1.1 422 Unprocessable Content

{ "message": "The number this group belongs to is not connected." }

An unknown or foreign group id gives:

HTTP/1.1 404 Not Found

{ "message": "The group was not found. Use the id from GET /groups." }

And an exhausted plan allowance gives 403 with the same wording as any other capacity refusal. See Errors and idempotency.

Retrying safely

Send an Idempotency-Key and a retry after a timeout replays the first response instead of posting to the group a second time. Nothing about a group send can be un-sent, which makes the key worth more here than almost anywhere else in this API — a duplicated message reaches every member again.

Scopes

GET /groups and POST /groups/sync need groups:read. POST /groups/{id}/messages needs groups:send.

groups:send is separate from messages:write deliberately. One call to a group reaches every member at once, none of whom opted in to anything with you — a key issued to send order confirmations should not also be able to post to a group of five hundred. Scopes are fixed when the key is issued, so a key created before these existed will keep returning 403 until you create a new one.

What this surface does not include

No creating, renaming, leaving or deleting groups — those are phone operations. No participant list, for the reason at the top of this page. No attachments to a group. And no inbound group messages: what other people post in a group is not received by MessageVia, so it does not reach your webhooks and does not appear in your inbox. Only what you send is recorded.

Was this guide useful? Report an issue