How to get a group invite link in Python with the Wapito WhatsApp API
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.
Invite-link handling in Python is a good fit for a nightly cron: a requests.get to read the code, a requests.delete to rotate it, and a redirect row in your database that the new code overwrites. The resolve-before-join pattern is a second small script, and both run comfortably as plain blocking code without any framework.
Before you start
pip install requestsos.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
Read the current invite code
The code is the tail of a chat.whatsapp.com link. Treat it as a credential rather than as a URL: anyone who holds it can join, so it belongs behind your own redirect rather than pasted into a public page you cannot update.
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 read the code with requests.get on the /invite path and pull response.json()["code"]; treat that string like a password in your logging configuration, because a debug log that prints the whole response has just written a group credential to disk. Store the code and build your public link from it at request time.
API reference for this stepRotate it on a schedule
Revoking generates a fresh code and invalidates every copy of the old link instantly. A nightly rotation means a link that leaks into a forum stops working within a day, without anyone having to notice that it leaked.
import requests url = "https://api.wapito.com/v1/groups/120363041234567890@g.us/invite" headers = {"Authorization": "Bearer wpt_YOUR_TOKEN"} response = requests.delete(url, headers=headers) print(response.json())In Python rotation is requests.delete on the same /invite path; the reply already contains the replacement code, so there is no second read. Update your redirect table inside the same try block, and if the update fails, raise, so the cron job reports a link that now points at a dead code rather than silently serving it.
API reference for this stepResolve a code before joining
Given a code somebody sent you, read the group's name, size and owner before deciding. This is how a bot avoids joining a group it has no business being in.
import requests url = "https://api.wapito.com/v1/groups/invite/HkQ2ZpL9vRtAeYm1" headers = {"Authorization": "Bearer wpt_YOUR_TOKEN"} response = requests.get(url, headers=headers) print(response.json())In Python resolving somebody else's code is requests.get on /groups/invite/{code} with the code passed through urllib.parse.quote; the dict that comes back carries the subject, the size and the owner. Decide in code with a plain if on those fields before the join call ever runs, and log the decision either way.
API reference for this stepAccept the invitation
Joining by code puts the linked number in the group as an ordinary member. Expect no admin rights, and expect the group's existing members to see the join as a system message.
import requests url = "https://api.wapito.com/v1/groups/invite/accept" payload = { "invite_code": "https://chat.whatsapp.com/HkQ2ZpL9vRtAeYm1" } headers = { "Authorization": "Bearer wpt_YOUR_TOKEN", "Content-Type": "application/json" } response = requests.post(url, json=payload, headers=headers) print(response.json())In Python joining is requests.post with json={"code": code}; the linked number becomes an ordinary member, so do not expect admin-only calls to work afterwards. Catch requests.HTTPError for the 404 not_found that a revoked or mistyped code produces and surface it as a normal outcome rather than a crash.
API reference for this step
The whole script
Every step above in one runnable file. Save it as group-invite-link.py, put your token in the environment, and run it.
"""Group Invite Link with the Wapito WhatsApp API.
Read the code, rotate it on a schedule, resolve unknown codes before accepting, and join by code when you mean to.
Run it with:
export WAPITO_TOKEN="wpt_..."
python group-invite-link.py
"""
import os
import requests
BASE_URL = "https://api.wapito.com/v1"
TOKEN = os.environ["WAPITO_TOKEN"]
# --- Read the current invite code ---
url = BASE_URL + "/groups/120363041234567890@g.us/invite"
headers = {"Authorization": "Bearer " + TOKEN}
response = requests.get(url, headers=headers)
print(response.json())
# --- Rotate it on a schedule ---
url = BASE_URL + "/groups/120363041234567890@g.us/invite"
headers = {"Authorization": "Bearer " + TOKEN}
response = requests.delete(url, headers=headers)
print(response.json())
# --- Resolve a code before joining ---
url = BASE_URL + "/groups/invite/HkQ2ZpL9vRtAeYm1"
headers = {"Authorization": "Bearer " + TOKEN}
response = requests.get(url, headers=headers)
print(response.json())
# --- Accept the invitation ---
url = BASE_URL + "/groups/invite/accept"
payload = { "invite_code": "https://chat.whatsapp.com/HkQ2ZpL9vRtAeYm1" }
headers = {
"Authorization": "Bearer " + TOKEN,
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, 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
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
- A retry decorator around the revoke call is dangerous: each retry after a timeout generates yet another code, so a flaky network can rotate the link three times in a minute and invalidate the copy you just stored.
- Python's logging of the requests.Response object shows only its status, but logging response.text on the invite endpoints prints the live code; keep those lines out of any log that ships to a third party.
Frequently asked questions
How often should I rotate the invite link?
It depends on where it lives. A link on a private confirmation page can sit for months; a link on a public social profile is worth rotating weekly, because that is where scrapers find them. Rotating behind your own redirect costs nothing to your users, so err on the frequent side.
Can I see who joined through a particular link?
Not directly - WhatsApp reports that someone joined, not which link they used. If you need attribution, use a distinct group per source, or put your own redirect in front of each published link and correlate the click with the join event that follows it.
Does joining by link make my number an admin?
No. Anyone joining by link arrives as an ordinary member, including an automation. If the bot needs to moderate, an existing admin has to promote it after it joins, which is worth building into the onboarding rather than discovering when the first write fails.
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.