How to create a group in Python with the Wapito WhatsApp API

Create the group, read it back by id, publish the invite link, and let the participants webhook keep your database in step.

A script, a token and an HTTP client is the whole stack. Everything below runs from a file you can execute today, and the same code moves to a worker or a container without changing shape.

Making a group from Python is three blocking requests calls in a row, which suits a script or a cron job better than a web handler. The library encodes the payload when you pass json=, the group comes back as a dict, and the one real trap is the id: keep it a str and never int() it.

Before you start

  • pip install requests
  • os.environ["WAPITO_TOKEN"]
  • A channel with a WhatsApp number linked to it, and its API token. Create one in the dashboard — the authentication guide shows where the token goes.

How it works

  1. Create the group

    Post a subject and the founding participants. The linked number becomes the creator and superadmin, so every later change - settings, admins, icon - is allowed without any extra step. Keep the founding list to people who already expect the group; adding strangers here is the fastest route to a report.

    import requests
    
    url = "https://api.wapito.com/v1/groups"
    
    payload = {
        "subject": "Acme Launch Team",
        "participants": ["+15551234567", "+15559876543"],
        "description": "Coordination for the Q3 launch. Keep it on topic."
    }
    headers = {
        "Authorization": "Bearer wpt_YOUR_TOKEN",
        "Content-Type": "application/json"
    }
    
    response = requests.post(url, json=payload, headers=headers)
    
    print(response.json())

    In Python this means requests.post(url, json=payload, headers=headers) with the subject and a list of participant strings; requests serialises the dict and sets the JSON content type itself. Read the new id with response.json()["id"] and call response.raise_for_status() first, because a 400 invalid_recipient still returns a parseable body.

    API reference for this step
  2. Read the group back

    Fetch the group by the id you just received and store that id as a string. It is longer than a 64-bit integer, so a numeric column or an eager JSON parser will silently corrupt it and every later call will answer 404.

    import requests
    
    url = "https://api.wapito.com/v1/groups/120363041234567890@g.us"
    
    headers = {"Authorization": "Bearer wpt_YOUR_TOKEN"}
    
    response = requests.get(url, headers=headers)
    
    print(response.json())

    In Python the read-back is a plain requests.get with the id interpolated into the path. Use an f-string on the str you kept from the create call, not on an int, and pass timeout=(5, 30) so a stalled socket cannot hang the worker. The group arrives as a dict with a participants list you can compare against what you sent.

    API reference for this step
  3. Share the invite link

    Read the invite code and publish the link rather than adding people directly. Joining by link is a deliberate act by the person, which is both better manners and materially safer for the number than pushing unknown participants into a group.

    import requests
    
    url = "https://api.wapito.com/v1/groups/120363041234567890@g.us/invite"
    
    headers = {"Authorization": "Bearer wpt_YOUR_TOKEN"}
    
    response = requests.get(url, headers=headers)
    
    print(response.json())

    In Python the invite call is another requests.get on the same id with /invite appended; the response dict carries the code and the full link. Compose the URL you publish with urllib.parse.urljoin against your own redirect domain rather than pasting the raw chat.whatsapp.com link into a template you cannot change later.

    API reference for this step
  4. Confirm membership over the webhook

    Each join or leave arrives as its own event with the participant and the action. Key your own records on that event rather than on the create response, because people who join by link never appear in it.

    Arrives on your webhook as groups.participants.

    In Python the FastAPI receiver reads await request.body() before parsing so the HMAC is computed over the exact bytes, then routes on payload["event"] == "groups.participants" and upserts the participant from payload["data"]. Hand the row to a queue or a background task and return the dict immediately; FastAPI serialises it as JSON.

The whole script

Every step above in one runnable file. Save it as create-group.py, put your token in the environment, and run it.

"""Create Group with the Wapito WhatsApp API.

Create the group, read it back by id, publish the invite link, and let the participants webhook keep your database in step.

Run it with:
    export WAPITO_TOKEN="wpt_..."
    python create-group.py
"""
import os

import requests

BASE_URL = "https://api.wapito.com/v1"
TOKEN = os.environ["WAPITO_TOKEN"]

# --- Create the group ---
url = BASE_URL + "/groups"

payload = {
    "subject": "Acme Launch Team",
    "participants": ["+15551234567", "+15559876543"],
    "description": "Coordination for the Q3 launch. Keep it on topic."
}
headers = {
    "Authorization": "Bearer " + TOKEN,
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

print(response.json())

# --- Read the group back ---
url = BASE_URL + "/groups/120363041234567890@g.us"

headers = {"Authorization": "Bearer " + TOKEN}

response = requests.get(url, headers=headers)

print(response.json())

# --- Share the invite link ---
url = BASE_URL + "/groups/120363041234567890@g.us/invite"

headers = {"Authorization": "Bearer " + TOKEN}

response = requests.get(url, headers=headers)

print(response.json())

Receive the webhook

FastAPI receiver for groups, groups.participants — it verifies the signature, answers immediately and does the work after. Install it with pip install fastapi uvicorn and save it as webhook.py.

import hashlib
import hmac
import os
import time

from fastapi import FastAPI, Header, HTTPException, Request

app = FastAPI()
SECRET = os.environ["WAPITO_WEBHOOK_SECRET"].encode()
EVENTS = ["groups", "groups.participants"]


def verify(raw: bytes, header: str | None) -> bool:
    """Checks the X-Wapito-Signature header: t=<ms>,v1=<hex hmac-sha256>."""
    if not header:
        return False
    parts = dict(p.split("=", 1) for p in header.split(",") if "=" in p)
    ts, sig = parts.get("t"), parts.get("v1")
    if not ts or not sig:
        return False
    if abs(time.time() * 1000 - int(ts)) > 300_000:  # 5 minute clock skew
        return False
    expected = hmac.new(SECRET, ts.encode() + b"." + raw, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, sig)


@app.post("/wapito")
async def wapito(request: Request, signature: str | None = Header(default=None, alias="X-Wapito-Signature")):
    raw = await request.body()
    if not verify(raw, signature):
        raise HTTPException(status_code=401, detail="bad signature")
    payload = await request.json()
    if payload["event"] in EVENTS:
        print(payload["event"], payload["data"])
    return {"ok": True}  # answer 2xx fast; do the real work in a queue
Every event, its payload and the retry rules

Errors you may hit

Gotchas

  • requests does not raise on a 4xx by itself. Call response.raise_for_status() or check response.ok, otherwise a 429 send_rate_limited silently looks like a successful send and your loop keeps hammering the queue.
  • json= and data= are not interchangeable. Pass json={...} so requests sets Content-Type: application/json and encodes UTF-8 for you; data= with a dict sends form encoding and the API answers 400 invalid_request.
  • The default requests timeout is None, which means a stalled connection hangs your worker forever. Always pass timeout=(5, 30) and treat a read timeout as "maybe delivered" - re-check with the message id before you send again.
  • A group id such as 120363000000000000@g.us is a string, not a number. Building it with an f-string from an int drops the precision and you get 404 not_found.
  • Emoji and accented text need no extra work in Python 3, but reading a CSV of numbers with the default encoding on Windows will mangle them - open files with encoding="utf-8".

Where to run it

  • Google Cloud Run (container, scale to zero, one revision per deploy)
  • Render background worker for the sender, Render web service for the webhook receiver
  • A 1 GB VPS with systemd for the worker and Caddy in front of the FastAPI receiver
  • AWS Lambda + API Gateway for the webhook receiver only (the sender needs a long-lived process for queue spacing)

Pitfalls in Python

  • Do not build the group id with an f-string from a number you parsed earlier: json.loads keeps it a str, but a pandas or sqlite round trip through an integer column rounds the digits and every later GET answers 404.
  • requests reuses nothing between the three calls unless you create a requests.Session(); with a plain requests.post each call opens a fresh TLS connection, which is fine for one group and noticeably slow when you create fifty in a loop.
  • If the participant list contains a number with spaces or a leading 00, Python will happily send it and the API will answer invalid_recipient; normalise with the phonenumbers package to E.164 before the post rather than catching the error afterwards.

Frequently asked questions

How many groups can I create in a day?

WhatsApp publishes no number, and anyone who quotes you one is guessing. What is observable is that new numbers get restricted far sooner than established ones. Start with a handful a day on a warmed-up number, watch for timelocks, and treat the first restriction as a signal to slow down rather than as a quota to probe.

Can I add people to the group as I create it?

Yes, the create call takes a participant list, but it is the riskiest way to fill a group. Many people have privacy settings that stop strangers adding them, so they will silently not appear, and those who do appear may report the number. Publishing the invite link is slower and much safer.

Does the linked number stay in the group forever?

It stays until it leaves or is removed. Because the creator holds the superadmin role, leaving a group you created hands nothing over automatically, so promote a human admin before the automation departs. Otherwise the group is left without anyone who can change its settings or membership.

Related

Try it on your own number

Create a channel, link a WhatsApp number by QR or pairing code, and call the API in a couple of minutes. The Sandbox plan is free and needs no card.