Guides

Create a WhatsApp group with an API (curl, Node, Python)

To create a WhatsApp group with an API, send POST /v1/sessions/{channel}/groups with a subject, then POST the people to add to /participants with action "add", then GET /invite-link. Every call carries the channel token as a Bearer header. The answer to the first call includes the group jid you use for the rest.

Facts (as of October 2026, API v1)

  • Base URL: https://api.wagate.dev/v1. Auth: Authorization: Bearer <channel token>.
  • Subject: 1 to 25 characters. Participants: up to 50 per request, phone numbers or JIDs.
  • The create answers 201 with the group. The participants call answers one result per person.
  • The free sandbox channel allows 5 group adds a day.

Steps

  1. Get a channel token. In the console, link a number to your free sandbox channel and create an API token for it. Keep the channel ID and the token in environment variables.
  2. Create the group. POST /groups with a subject. The answer is the group, including its jid.
  3. Add people. POST /groups/{jid}/participants with action "add" and the people to add. Read the result of each person.
  4. Get the invite link. GET /groups/{jid}/invite-link and share the invite_link.

Before you start

You need a channel ID and its token. The commands below read them from CHANNEL_ID and CHANNEL_TOKEN. The examples use placeholders for phone numbers: use full international digits, no plus sign.

curl

curl
API=https://api.wagate.dev/v1
BASE="$API/sessions/$CHANNEL_ID"
AUTH="Authorization: Bearer $CHANNEL_TOKEN"

curl -s -X POST "$BASE/groups" \
  -H "$AUTH" \
  -H "Content-Type: application/json" \
  -d '{"subject":"Team","participants":[]}'

# Put the "jid" from the answer above here, for example [email protected]
[email protected]

curl -s -X POST "$BASE/groups/$GROUP/participants" \
  -H "$AUTH" \
  -H "Content-Type: application/json" \
  -d '{"action":"add","participants":["66XXXXXXXXX"]}'

curl -s -X GET "$BASE/groups/$GROUP/invite-link" \
  -H "$AUTH"

Node.js

Node 20 or later, which has fetch built in. Save it as sample.mjs, because it uses top-level await. The script runs the three calls in order and prints each answer.

Node.js
const API = 'https://api.wagate.dev/v1';
const { CHANNEL_ID, CHANNEL_TOKEN } = process.env;
const BASE = `${API}/sessions/${CHANNEL_ID}`;
const headers = {
  Authorization: `Bearer ${CHANNEL_TOKEN}`,
  'Content-Type': 'application/json',
};

async function call(method, path, body) {
  const res = await fetch(`${BASE}${path}`, {
    method,
    headers,
    body: body === undefined ? undefined : JSON.stringify(body),
  });
  const data = await res.json();
  if (!res.ok) throw new Error(`${res.status} ${data.code}: ${data.error}`);
  return data;
}

const group = await call('POST', `/groups`, {"subject":"Team","participants":[]});
console.log(group);
const added = await call('POST', `/groups/${group.jid}/participants`, {"action":"add","participants":["66XXXXXXXXX"]});
console.log(added);
const invite = await call('GET', `/groups/${group.jid}/invite-link`);
console.log(invite);

Python

Python
import os
import requests

API = 'https://api.wagate.dev/v1'
BASE = f"{API}/sessions/{os.environ['CHANNEL_ID']}"
HEADERS = {'Authorization': f"Bearer {os.environ['CHANNEL_TOKEN']}"}


def call(method, path, body=None):
    res = requests.request(method, BASE + path, headers=HEADERS, json=body, timeout=30)
    data = res.json()
    if not res.ok:
        raise RuntimeError(f"{res.status_code} {data.get('code')}: {data.get('error')}")
    return data


group = call('POST', f"/groups", {
  "subject": "Team",
  "participants": []
})
print(group)
added = call('POST', f"/groups/{group['jid']}/participants", {
  "action": "add",
  "participants": [
    "66XXXXXXXXX"
  ]
})
print(added)
invite = call('GET', f"/groups/{group['jid']}/invite-link")
print(invite)

Read the add results

The participants call answers 200 with one result per person. A person who fails does not fail the others.

JSON
{
  "results": [
    {
      "jid": "66XXXXXXXXX",
      "status": "ok",
      "matched_by": "a"
    }
  ]
}

More you can do with a group

MethodPathWhat it does
GET/v1/sessions/{channel}/groupsList groups
POST/v1/sessions/{channel}/groupsCreate a group
GET/v1/sessions/{channel}/groups/{group}Get a group
PUT/v1/sessions/{channel}/groups/{group}/descriptionSet or clear a group's description
GET/v1/sessions/{channel}/groups/{group}/invite-linkGet a group's invite link
POST/v1/sessions/{channel}/groups/{group}/participantsAdd, remove, promote or demote participants
PUT/v1/sessions/{channel}/groups/{group}/settingsChange a group's settings
PUT/v1/sessions/{channel}/groups/{group}/subjectSet a group's subject

Retry rules

The API has no idempotency key. A 500, 502 or 503 on a write can leave it applied. List or read the group first, and never re-create a group blindly. A person with error_409 is already in the group, which for an add is a success.

Questions

What does the group create request need?
Only a subject of 1 to 25 characters. participants (up to 50) and parent_jid (to create the group inside a community) are optional.
Do I add people in the create call or after?
Either. You can name up to 50 people in the create call, or add them afterwards with the participants call and action "add".
What happens if one person cannot be added?
The answer has one result per person, so the others still go through. The failed person has a status such as error_403 with a sentence that says why.
How do I find out when someone joins or leaves?
Subscribe a webhook receiver to group.participants. Each delivery is signed, so verify the signature before you trust it.

Free sandbox, forever. Add a real number when you're ready.

Start in the free sandbox

WaGate is an independent WhatsApp API for developers: send and receive messages, automate groups and communities, and get signed webhooks with a free sandbox. Not affiliated with WhatsApp or Meta. You link your own number.