How to get a group invite link in Python 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.

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 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 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 step
  2. Rotate 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 step
  3. Resolve 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 step
  4. Accept 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
Every event, its payload and the retry rules

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.