Create and manage WhatsApp Business groups
WhatsApp Business groups are groups a business creates on its own Cloud API number through Meta's Groups API. They are not personal WhatsApp groups: Tyxter does not connect to, read, join, import or mirror groups created in the WhatsApp or WhatsApp Business app.
Read WhatsApp Business groups are not personal WhatsApp groups first if you were asked to connect existing groups, add people by phone number or put more than eight people in one group: that page explains why none of those works with WhatsApp Business groups.
Beta. This guide walks one group through its lifecycle, sandbox first and production second. It explains how each step works, not whether it is available through Tyxter today: the WhatsApp Business groups API reference is the one place where availability is kept current, and it has every field, status and error.
Facts marked (heard) come from Meta's Groups API documentation and have not yet been observed on a live group through Tyxter. Facts marked (assumed) are Tyxter's reading where Meta's documentation is silent.
Before you start
- An API key with the scopes the steps use:
groups:readfor the eligibility read and the group reads (steps 1, 3, 5, 6 and 7);groups:writefor the eligibility check, the create, the sandbox participant simulation, the invite-link reset and the delete (steps 1, 2, 5, 6 and 7);messages:sendfor the invite message with POST /v1/messages (step 4); and, to receive the group events (next item),webhooks:writeto update an existing endpoint's subscriptions orwebhooks:readfor the listen route. Finding the phone number id (next item) takesphone_numbers:readfor GET /v1/phone-numbers, orsandbox:writefor GET /v1/sandbox/quickstart. Atx_sandbox_key works against sandbox and atx_live_key against production; the calls are the same, except the sandbox-only participant simulation in step 5. - A Tyxter phone number. Every groups call that takes
phone_number_idexpects the Tyxter phone number id (theidon GET /v1/phone-numbers), never Meta's; the calls on one group (retrieve, delete, reset and the sandbox simulation) identify it bygroup_idalone. In sandbox, GET /v1/sandbox/quickstart returns the phone number id assender.default_sender_id. - A way to receive the eight group events: a webhook endpoint subscribed to them, or the listen route GET /v1/webhook-events/listen. Existing endpoints are not subscribed to new event types, and PATCH /v1/webhook-endpoints/{webhook_endpoint_id} replaces the whole
subscribed_eventslist: send the types the endpoint already has plus the eightgroup.*types. The Webhooks guide covers signatures, retries and listening.
1. Check eligibility
Read the stored answer with GET /v1/groups/eligibility?phone_number_id={phone_number_id}. A sandbox number answers eligible. A production number answers not_checked until a check completes: request one with POST /v1/groups/eligibility-checks, a Tyxter worker then reads the number's facts from Meta, and the read shows the new answer once checked_at is later than check_requested_at.
Of the eligibility answers, only a stored not_eligible refuses a create, with 422 group_phone_number_not_eligible and the reasons Meta reported. unknown, not_checked and read_failed do not refuse it.
2. Create the group
POST /v1/groups with phone_number_id and subject (the group name), plus an Idempotency-Key. The answer is 202 with the group's Tyxter id and status pending. Keep the id: every later call uses it, never Meta's group id. A retry with the same key and body returns that first response again, still pending, and creates no second group; read the group to see where it is now.
3. Wait for group.created
A Tyxter worker picks the create up.
- Sandbox: the group becomes
activeby itself, with a sandboxprovider_group_idand a sandboxinvite_linkthat cannot be used to join a real group (assumed). It becomesfailedinstead if its number can no longer host a group when the worker runs. - Production: Tyxter sends the create to Meta, and Meta reports the outcome later by webhook (heard). Tyxter applies that report: the group becomes
activewith Meta'sprovider_group_idandinvite_link, orfailed. Under "Available today", the API reference says when that report reaches Tyxter and which cases leave a grouppending.
When the group leaves pending, Tyxter produces group.created, or group.create_failed with failure.code saying why, for the webhook endpoints subscribed to them; the errors page lists every group failure.code. Instead of waiting for the event you can poll GET /v1/groups/{group_id} until status leaves pending, or list the environment's groups with GET /v1/groups, filtered by phone_number_id or status. Tyxter promises no completion time, and a pending group cannot be deleted yet (409 group_not_active).
4. Share the invite link
The invite link is the only way anyone joins a WhatsApp Business group: the business cannot add people (heard). Send invite_link to each person as an ordinary 1:1 WhatsApp message with POST /v1/messages, under the same service-window and template rules as any other send (Messages API). The invite is a message to that person, not a message into the group.
Treat invite_link like a password: anyone who has it can join the group. The same goes for the payloads of the six group lifecycle events, which carry it.
5. Follow participants
- Sandbox: simulate a person joining or leaving an
activegroup with POST /v1/sandbox/groups/{group_id}/participants and{ "wa_id": "...", "action": "join" }(or"remove"). The sandbox refuses a ninth participant with409 group_participant_limit_reached, the cap Meta documents (heard). - Production: a person joins by opening the link, and Meta reports each join and removal by webhook (heard), under the same condition as the create outcome.
A join Tyxter records produces a group.participant_joined event for the endpoints subscribed to it, and a removal a group.participant_removed event, whose initiated_by says whether the participant left (participant) or the business removed them (business). Both carry the participant's WhatsApp ID as participant.wa_id. When a contact erasure (DELETE /v1/contacts/{id}) matches the participant, Tyxter erases their recorded joins and removals: one whose event was not yet produced then produces none, and the listen route no longer returns their events; the API reference's Participants section says which WhatsApp IDs an erasure matches. Delivery follows the Webhooks guide's rules. GET /v1/groups/{group_id} lists the current participants. Events can arrive late, out of order or twice; the API reference's Participants section explains how Tyxter orders them.
6. Reset the invite link
Only an active group can be reset. POST /v1/groups/{group_id}/invite-link/reset answers 202 with the group still active and still showing the old link. A Tyxter worker then resets it. On success invite_link holds the new link (in production the one Meta returns; earlier links stop working, heard) and Tyxter produces group.invite_link_reset. When Tyxter records a failed reset, it produces group.invite_link_reset_failed with failure.code. Where that failure lands depends on the group's status when it is recorded: on an active group the group's failure shows it too; on a deleting group only the event carries it and the group keeps the delete's failure (a refused delete returns the group to active, so a reset failure recorded after that lands on the group). A reset replaced by a newer accepted reset, or not yet answered when the group becomes deleted, produces no event. The API reference says what each failure means for the stored link. Read the group to see the new link, and share it again.
7. Delete the group
DELETE /v1/groups/{group_id} answers 202. An active group becomes deleting, then deleted once the delete finishes, with group.deleted; if Meta refuses the delete, the group is active again with failure set, with group.delete_failed. In production a delete can also stay deleting, and while it does it produces neither event: when the call to Meta fails without a definite answer (for example a timeout), the group's failure.code is provider_error and no group.deleted or group.delete_failed follows unless Meta later reports the outcome. A delete Tyxter has not been able to send, or one still waiting for Meta's outcome, also stays deleting, with failure null. Read the group rather than waiting for an event; the API reference lists when a delete stays deleting. A failed group becomes deleted at once, with no call to Meta. A deleted group stays readable with status deleted. The delete keeps working when WhatsApp Business groups are disabled in feature controls, so you can always delete the groups you created.
Sandbox first, production second
The routes, request bodies, responses, error codes and events are the same in both. What differs:
- In sandbox the create, the delete and the invite-link reset finish by themselves, and participants come only from the simulation route. A sandbox key never receives
group.delete_failedorgroup.invite_link_reset_failed, and receivesgroup.create_failedonly when the sandbox number is released, disconnected or removed before the worker runs. - In production the outcome of each create, delete and reset Tyxter sends comes from Meta. Meta's own limits apply: 8 participants per group and 10,000 groups per business phone number (heard). Tyxter enforces neither in production; when Meta refuses an operation, the group's
failure.codeisgroup_provider_rejectedwith Meta's reason.
Example
import { randomUUID } from 'node:crypto';
import type { GroupParticipantWebhookEnvelope, GroupWebhookEnvelope } from '@tyxter/sdk-js';
// 1. Eligibility: a sandbox number answers 'eligible'.
const eligibility = await client.groups.retrieveEligibility({ phone_number_id: phoneNumberId });
// 2. Create: 202 with status 'pending'.
const group = await client.groups.create(
{ phone_number_id: phoneNumberId, subject: 'Support team' },
{ idempotencyKey: randomUUID() },
);
// 3. React to the outcome in your webhook handler, or poll client.groups.retrieve(group.id)
// until status leaves 'pending'. Steps 4 to 7 need the group to be 'active'.
async function onGroupEvent(event: GroupWebhookEnvelope) {
if (event.type === 'group.created') await onActiveGroup(event.data.group_id);
if (event.type === 'group.create_failed') console.log(event.data.failure?.code);
}
async function onActiveGroup(groupId: string) {
// Read the group first: an event can arrive late, after the group changed again.
const active = await client.groups.retrieve(groupId);
if (active.status !== 'active' || !active.invite_link) return;
// 4. Share the invite link with an ordinary 1:1 message.
await client.whatsapp.sendText(
{ from: phoneNumberId, to: recipient, body: `Join our group: ${active.invite_link}` },
{ idempotencyKey: randomUUID() },
);
// 5. Sandbox only (a production key gets 400 sandbox_group_participant_sandbox_only):
// simulate a person joining.
await client.sandbox.groups.simulateParticipant(
groupId,
{ wa_id: '5511999990001', action: 'join' },
{ idempotencyKey: randomUUID() },
);
}
function onParticipantEvent(event: GroupParticipantWebhookEnvelope) {
console.log(event.type, event.data.participant.wa_id, event.data.participant_count);
}
// 6. Later, on an active group: reset the invite link; read the group again for the new one.
async function resetInvite(groupId: string) {
await client.groups.resetInviteLink(groupId, { idempotencyKey: randomUUID() });
}
// 7. When you are done: delete the group once it is 'active' (or 'failed').
async function deleteGroup(groupId: string) {
await client.groups.delete(groupId, { idempotencyKey: randomUUID() });
}The same create and read with curl:
curl -X POST "https://api.tyxter.com/v1/groups" \
-H "authorization: Bearer $TYXTER_API_KEY" \
-H "content-type: application/json" \
-H "idempotency-key: $(uuidgen)" \
-d '{"phone_number_id":"'"$PHONE_NUMBER_ID"'","subject":"Support team"}'
curl "https://api.tyxter.com/v1/groups/{group_id}" \
-H "authorization: Bearer $TYXTER_API_KEY"Where to go next
- WhatsApp Business groups API reference: every route, field, status and error, and what is available today.
- Webhooks: signatures, retries, listening and the group events.
- Errors: every group
failure.codeand HTTP error code. - WhatsApp Business groups are not personal WhatsApp groups: what this feature is not.