How to add group members in Python with the Wapito WhatsApp API

List who is actually in the group, add the missing people, remove the ones who left, and reconcile from the participants webhook.

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.

Managing membership from Python is a reconciliation job, so the natural shape is a scheduled script that reads the live list with requests, diffs it against your own table, and issues the adds and removes it finds. Each call returns a dict, the add call returns a per-number result list, and a requests.Session keeps the connection open across the batch.

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. Read the current membership

    Always start from the real list rather than from your own copy. Members join by link, leave on their own, and are removed by other admins, so a database that has not seen a participants event in a while is usually out of date.

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

    In Python the roll call is requests.get on the /participants path, and the response is a list of dicts with the participant identity and role. Convert it to a set of identities before you compare: a list comprehension over response.json() is enough, and the set difference in either direction gives you the adds and the removes.

    API reference for this step
  2. Add the people who are missing

    Send the numbers in a single call and read the per-participant result. Some will be added, some will be invited instead because their privacy settings forbid direct adds, and some will fail outright - the response says which is which.

    import requests
    
    url = "https://api.wapito.com/v1/groups/120363041234567890@g.us/participants"
    
    payload = { "participants": ["+15551234567", "+15559876543"] }
    headers = {
        "Authorization": "Bearer wpt_YOUR_TOKEN",
        "Content-Type": "application/json"
    }
    
    response = requests.post(url, json=payload, headers=headers)
    
    print(response.json())

    In Python the add is requests.post with json={"participants": [...]} holding every number for this run; the reply is a list with one entry per number and a status on each. Iterate that list rather than checking response.ok, because the call returns 200 even when some people were only invited or were refused.

    API reference for this step
  3. Remove someone who has left the team

    Removal is immediate and the person sees that they were removed. Do it from a scheduled job that reads your own source of truth, so nobody is removed because of a transient CRM sync failure.

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

    In Python a removal is requests.delete on /participants/{pid} with the participant id quoted into the path, one call per person. Wrap it in try/except requests.HTTPError after raise_for_status() so a single 404 for someone who already left does not stop the rest of the sweep.

    API reference for this step
  4. Reconcile from the webhook

    Every join, leave, add and remove arrives as an event carrying the action and the participant, who may be a linked identity rather than a phone number. Apply it to your own records so the two never drift apart.

    Arrives on your webhook as groups.participants.

    In Python the FastAPI handler receives the participants event with an action field and a participant that may be a linked identity; match on that identity first and fall back to the phone number only when it is present. Do the upsert in a BackgroundTask so the handler returns its 2xx within milliseconds.

The whole script

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

"""Group Participants with the Wapito WhatsApp API.

List who is actually in the group, add the missing people, remove the ones who left, and reconcile from the participants webhook.

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

import requests

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

# --- Read the current membership ---
url = BASE_URL + "/groups/120363041234567890@g.us/participants"

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

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

print(response.json())

# --- Add the people who are missing ---
url = BASE_URL + "/groups/120363041234567890@g.us/participants"

payload = { "participants": ["+15551234567", "+15559876543"] }
headers = {
    "Authorization": "Bearer " + TOKEN,
    "Content-Type": "application/json"
}

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

print(response.json())

# --- Remove someone who has left the team ---
url = BASE_URL + "/groups/120363041234567890@g.us/participants/+15551234567"

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

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

print(response.status_code)

Receive the webhook

FastAPI receiver for 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.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

  • The per-participant result list is the truth and response.ok is not: requests reports success for the batch while individual entries say invited or failed. A script that only calls raise_for_status() will think everyone was added.
  • A set difference on phone numbers silently misses people who appear as linked identities in the live list, so you add them again on every run. Key your comparison on the identity field the API returns, not on a number you reformatted.
  • Removing members inside a for loop with no pause is the pattern most likely to look like an attack; add time.sleep between deletes and cap the number of removals per run in your own code.

Frequently asked questions

Why are some people invited instead of added?

WhatsApp lets everyone choose who may add them to groups. If a person has restricted that to their contacts, an add from an unknown number becomes an invitation they have to accept. The API reports this per participant, so your code can tell the difference between someone who is in the group and someone who has merely been asked.

Is there a limit to how many I can add at once?

The group itself has a member ceiling set by WhatsApp, and practical experience says that adding many people in quick succession draws attention regardless of the ceiling. Add in small batches with a pause between them, and if the group is large, publish an invite link and let people join at their own pace.

Can I re-add someone who left?

Yes, technically, but think about whether you should. Someone who left a group and is immediately put back in is very likely to report the number, and repeated re-adds of the same person are a strong abuse signal. Send them the invite link instead and let them decide.

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.