[{"data":1,"prerenderedAt":155},["ShallowReactive",2],{"pseo:tutorials:group-participants:python":3,"highlight:tutorial:group-participants:python":144},{"ban_risk":4,"ban_risk_note":5,"deploy_targets":6,"errors":11,"faqs":18,"feature":29,"full_script":33,"gotchas":37,"intro_note":43,"language":44,"pitfalls":48,"prerequisites":52,"related":55,"seo":82,"siblings":101,"step_summary":108,"steps":109,"tier":137,"url":95,"webhook_receiver":138},"high","Adding people who never asked to be in a group is the behaviour WhatsApp punishes hardest, because it is what bulk marketing looks like from the inside. Prefer invite links; when you must add directly, add small batches of people who expect it, spread over time, and stop immediately if the API starts reporting that participants are being invited rather than added.",[7,8,9,10],"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)",[12,15],{"title":13,"url":14},"invalid_request (400)","\u002Fdocs\u002Ferrors\u002Finvalid-request\u002F",{"title":16,"url":17},"forbidden (403)","\u002Fdocs\u002Ferrors\u002Fforbidden\u002F",[19,23,26],{"a":20,"q":21,"source":22},"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.","Why are some people invited instead of added?","features.json",{"a":24,"q":25,"source":22},"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.","Is there a limit to how many I can add at once?",{"a":27,"q":28,"source":22},"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.","Can I re-add someone who left?",{"name":30,"slug":31,"url":32},"Group Participants","group-participants","\u002Fwhatsapp-api\u002Fgroup-participants\u002F",{"code":34,"filename":35,"lang":36},"\"\"\"Group Participants with the Wapito WhatsApp API.\n\nList who is actually in the group, add the missing people, remove the ones who left, and reconcile from the participants webhook.\n\nRun it with:\n    export WAPITO_TOKEN=\"wpt_...\"\n    python group-participants.py\n\"\"\"\nimport os\n\nimport requests\n\nBASE_URL = \"https:\u002F\u002Fapi.wapito.com\u002Fv1\"\nTOKEN = os.environ[\"WAPITO_TOKEN\"]\n\n# --- Read the current membership ---\nurl = BASE_URL + \"\u002Fgroups\u002F120363041234567890@g.us\u002Fparticipants\"\n\nheaders = {\"Authorization\": \"Bearer \" + TOKEN}\n\nresponse = requests.get(url, headers=headers)\n\nprint(response.json())\n\n# --- Add the people who are missing ---\nurl = BASE_URL + \"\u002Fgroups\u002F120363041234567890@g.us\u002Fparticipants\"\n\npayload = { \"participants\": [\"+15551234567\", \"+15559876543\"] }\nheaders = {\n    \"Authorization\": \"Bearer \" + TOKEN,\n    \"Content-Type\": \"application\u002Fjson\"\n}\n\nresponse = requests.post(url, json=payload, headers=headers)\n\nprint(response.json())\n\n# --- Remove someone who has left the team ---\nurl = BASE_URL + \"\u002Fgroups\u002F120363041234567890@g.us\u002Fparticipants\u002F+15551234567\"\n\nheaders = {\"Authorization\": \"Bearer \" + TOKEN}\n\nresponse = requests.delete(url, headers=headers)\n\nprint(response.status_code)\n\n","group-participants.py","python",[38,39,40,41,42],"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\u002Fjson 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\".","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.",{"family":45,"name":46,"slug":36,"url":47},"scripting","Python","\u002Fwhatsapp-bot\u002Fpython\u002F",[49,50,51],"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.",[53,54],"pip install requests","os.environ[\"WAPITO_TOKEN\"]",{"language_hub":47,"operations":56,"pillar":32,"same_language_features":66},[57,60,63],{"title":58,"url":59},"GET \u002Fgroups\u002F{id}\u002Fparticipants","\u002Fdocs\u002Fapi\u002Fgroups\u002Flist-group-participants\u002F",{"title":61,"url":62},"POST \u002Fgroups\u002F{id}\u002Fparticipants","\u002Fdocs\u002Fapi\u002Fgroups\u002Fadd-group-participants\u002F",{"title":64,"url":65},"DELETE \u002Fgroups\u002F{id}\u002Fparticipants\u002F{pid}","\u002Fdocs\u002Fapi\u002Fgroups\u002Fremove-group-participant\u002F",[67,70,73,76,79],{"title":68,"url":69},"Create Group","\u002Fwhatsapp-api\u002Fcreate-group\u002Fpython\u002F",{"title":71,"url":72},"Group Admins","\u002Fwhatsapp-api\u002Fgroup-admins\u002Fpython\u002F",{"title":74,"url":75},"Group Invite Link","\u002Fwhatsapp-api\u002Fgroup-invite-link\u002Fpython\u002F",{"title":77,"url":78},"Group Settings","\u002Fwhatsapp-api\u002Fgroup-settings\u002Fpython\u002F",{"title":80,"url":81},"Group Join Requests","\u002Fwhatsapp-api\u002Fgroup-join-requests\u002Fpython\u002F",{"breadcrumb":83,"canonical":96,"description":97,"h1":98,"lastmod":99,"title":100},[84,87,90,93,94],{"name":85,"url":86},"Home","\u002F",{"name":88,"url":89},"WhatsApp API","\u002Fwhatsapp-api\u002F",{"name":91,"url":92},"Groups","\u002Fwhatsapp-api\u002Fgroups\u002F",{"name":30,"url":32},{"name":46,"url":95},"\u002Fwhatsapp-api\u002Fgroup-participants\u002Fpython\u002F","https:\u002F\u002Fwapito.com\u002Fwhatsapp-api\u002Fgroup-participants\u002Fpython\u002F","How to add group members in Python with the Wapito WhatsApp API. 4 steps with runnable code, a FastAPI webhook receiver and the errors to expect.","How to add group members in Python with the Wapito WhatsApp API","2026-09-16","Add Members in Python (WhatsApp API Tutorial) | Wapito",[102,105],{"title":103,"url":104},"Node.js","\u002Fwhatsapp-api\u002Fgroup-participants\u002Fnodejs\u002F",{"title":106,"url":107},"PHP","\u002Fwhatsapp-api\u002Fgroup-participants\u002Fphp\u002F","List who is actually in the group, add the missing people, remove the ones who left, and reconcile from the participants webhook.",[110,118,125,132],{"body":111,"note":112,"operationId":113,"operation_url":59,"snippet":114,"title":116,"webhook":117},"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.","In Python the roll call is requests.get on the \u002Fparticipants 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.","listGroupParticipants",{"code":115,"lang":36},"import requests\n\nurl = \"https:\u002F\u002Fapi.wapito.com\u002Fv1\u002Fgroups\u002F120363041234567890@g.us\u002Fparticipants\"\n\nheaders = {\"Authorization\": \"Bearer wpt_YOUR_TOKEN\"}\n\nresponse = requests.get(url, headers=headers)\n\nprint(response.json())","Read the current membership",null,{"body":119,"note":120,"operationId":121,"operation_url":62,"snippet":122,"title":124,"webhook":117},"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.","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.","addGroupParticipants",{"code":123,"lang":36},"import requests\n\nurl = \"https:\u002F\u002Fapi.wapito.com\u002Fv1\u002Fgroups\u002F120363041234567890@g.us\u002Fparticipants\"\n\npayload = { \"participants\": [\"+15551234567\", \"+15559876543\"] }\nheaders = {\n    \"Authorization\": \"Bearer wpt_YOUR_TOKEN\",\n    \"Content-Type\": \"application\u002Fjson\"\n}\n\nresponse = requests.post(url, json=payload, headers=headers)\n\nprint(response.json())","Add the people who are missing",{"body":126,"note":127,"operationId":128,"operation_url":65,"snippet":129,"title":131,"webhook":117},"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.","In Python a removal is requests.delete on \u002Fparticipants\u002F{pid} with the participant id quoted into the path, one call per person. Wrap it in try\u002Fexcept requests.HTTPError after raise_for_status() so a single 404 for someone who already left does not stop the rest of the sweep.","removeGroupParticipant",{"code":130,"lang":36},"import requests\n\nurl = \"https:\u002F\u002Fapi.wapito.com\u002Fv1\u002Fgroups\u002F120363041234567890@g.us\u002Fparticipants\u002F+15551234567\"\n\nheaders = {\"Authorization\": \"Bearer wpt_YOUR_TOKEN\"}\n\nresponse = requests.delete(url, headers=headers)\n\nprint(response.status_code)","Remove someone who has left the team",{"body":133,"note":134,"operationId":117,"operation_url":117,"snippet":117,"title":135,"webhook":136},"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.","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.","Reconcile from the webhook","groups.participants","t2",{"code":139,"events":140,"filename":141,"framework":142,"install":143,"lang":36},"import hashlib\nimport hmac\nimport os\nimport time\n\nfrom fastapi import FastAPI, Header, HTTPException, Request\n\napp = FastAPI()\nSECRET = os.environ[\"WAPITO_WEBHOOK_SECRET\"].encode()\nEVENTS = [\"groups.participants\"]\n\n\ndef verify(raw: bytes, header: str | None) -> bool:\n    \"\"\"Checks the X-Wapito-Signature header: t=\u003Cms>,v1=\u003Chex hmac-sha256>.\"\"\"\n    if not header:\n        return False\n    parts = dict(p.split(\"=\", 1) for p in header.split(\",\") if \"=\" in p)\n    ts, sig = parts.get(\"t\"), parts.get(\"v1\")\n    if not ts or not sig:\n        return False\n    if abs(time.time() * 1000 - int(ts)) > 300_000:  # 5 minute clock skew\n        return False\n    expected = hmac.new(SECRET, ts.encode() + b\".\" + raw, hashlib.sha256).hexdigest()\n    return hmac.compare_digest(expected, sig)\n\n\n@app.post(\"\u002Fwapito\")\nasync def wapito(request: Request, signature: str | None = Header(default=None, alias=\"X-Wapito-Signature\")):\n    raw = await request.body()\n    if not verify(raw, signature):\n        raise HTTPException(status_code=401, detail=\"bad signature\")\n    payload = await request.json()\n    if payload[\"event\"] in EVENTS:\n        print(payload[\"event\"], payload[\"data\"])\n    return {\"ok\": True}  # answer 2xx fast; do the real work in a queue\n",[136],"webhook.py","FastAPI","pip install fastapi uvicorn",[145,147,149,151,153],{"label":46,"id":36,"lang":36,"code":115,"html":146},"\u003Cpre class=\"shiki shiki-themes github-light-default github-dark-default\" style=\"background-color:#ffffff;--shiki-dark-bg:#0d1117;color:#1f2328;--shiki-dark:#e6edf3\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\">import\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> requests\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">url \u003C\u002Fspan>\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\">=\u003C\u002Fspan>\u003Cspan style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> \"https:\u002F\u002Fapi.wapito.com\u002Fv1\u002Fgroups\u002F120363041234567890@g.us\u002Fparticipants\"\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">headers \u003C\u002Fspan>\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\">=\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {\u003C\u002Fspan>\u003Cspan style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Authorization\"\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: \u003C\u002Fspan>\u003Cspan style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Bearer wpt_YOUR_TOKEN\"\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">response \u003C\u002Fspan>\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\">=\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> requests.get(url, \u003C\u002Fspan>\u003Cspan style=\"color:#953800;--shiki-dark:#FFA657\">headers\u003C\u002Fspan>\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\">=\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">headers)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#0550AE;--shiki-dark:#79C0FF\">print\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(response.json())\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>",{"label":46,"id":36,"lang":36,"code":123,"html":148},"\u003Cpre class=\"shiki shiki-themes github-light-default github-dark-default\" style=\"background-color:#ffffff;--shiki-dark-bg:#0d1117;color:#1f2328;--shiki-dark:#e6edf3\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\">import\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> requests\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">url \u003C\u002Fspan>\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\">=\u003C\u002Fspan>\u003Cspan style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> \"https:\u002F\u002Fapi.wapito.com\u002Fv1\u002Fgroups\u002F120363041234567890@g.us\u002Fparticipants\"\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">payload \u003C\u002Fspan>\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\">=\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { \u003C\u002Fspan>\u003Cspan style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"participants\"\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: [\u003C\u002Fspan>\u003Cspan style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"+15551234567\"\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, \u003C\u002Fspan>\u003Cspan style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"+15559876543\"\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">] }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">headers \u003C\u002Fspan>\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\">=\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#0A3069;--shiki-dark:#A5D6FF\">    \"Authorization\"\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: \u003C\u002Fspan>\u003Cspan style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Bearer wpt_YOUR_TOKEN\"\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#0A3069;--shiki-dark:#A5D6FF\">    \"Content-Type\"\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: \u003C\u002Fspan>\u003Cspan style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"application\u002Fjson\"\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">response \u003C\u002Fspan>\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\">=\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> requests.post(url, \u003C\u002Fspan>\u003Cspan style=\"color:#953800;--shiki-dark:#FFA657\">json\u003C\u002Fspan>\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\">=\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">payload, \u003C\u002Fspan>\u003Cspan style=\"color:#953800;--shiki-dark:#FFA657\">headers\u003C\u002Fspan>\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\">=\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">headers)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#0550AE;--shiki-dark:#79C0FF\">print\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(response.json())\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>",{"label":46,"id":36,"lang":36,"code":130,"html":150},"\u003Cpre class=\"shiki shiki-themes github-light-default github-dark-default\" style=\"background-color:#ffffff;--shiki-dark-bg:#0d1117;color:#1f2328;--shiki-dark:#e6edf3\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\">import\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> requests\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">url \u003C\u002Fspan>\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\">=\u003C\u002Fspan>\u003Cspan style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> \"https:\u002F\u002Fapi.wapito.com\u002Fv1\u002Fgroups\u002F120363041234567890@g.us\u002Fparticipants\u002F+15551234567\"\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">headers \u003C\u002Fspan>\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\">=\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {\u003C\u002Fspan>\u003Cspan style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Authorization\"\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: \u003C\u002Fspan>\u003Cspan style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Bearer wpt_YOUR_TOKEN\"\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">response \u003C\u002Fspan>\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\">=\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> requests.delete(url, \u003C\u002Fspan>\u003Cspan style=\"color:#953800;--shiki-dark:#FFA657\">headers\u003C\u002Fspan>\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\">=\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">headers)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#0550AE;--shiki-dark:#79C0FF\">print\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(response.status_code)\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>",{"label":46,"id":36,"lang":36,"code":34,"html":152},"\u003Cpre class=\"shiki shiki-themes github-light-default github-dark-default\" style=\"background-color:#ffffff;--shiki-dark-bg:#0d1117;color:#1f2328;--shiki-dark:#e6edf3\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"\"\"Group Participants with the Wapito WhatsApp API.\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#0A3069;--shiki-dark:#A5D6FF\">List who is actually in the group, add the missing people, remove the ones who left, and reconcile from the participants webhook.\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#0A3069;--shiki-dark:#A5D6FF\">Run it with:\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#0A3069;--shiki-dark:#A5D6FF\">    export WAPITO_TOKEN=\"wpt_...\"\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#0A3069;--shiki-dark:#A5D6FF\">    python group-participants.py\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"\"\"\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\">import\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> os\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\">import\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> requests\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#0550AE;--shiki-dark:#79C0FF\">BASE_URL\u003C\u002Fspan>\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =\u003C\u002Fspan>\u003Cspan style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> \"https:\u002F\u002Fapi.wapito.com\u002Fv1\"\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#0550AE;--shiki-dark:#79C0FF\">TOKEN\u003C\u002Fspan>\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> os.environ[\u003C\u002Fspan>\u003Cspan style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"WAPITO_TOKEN\"\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6E7781;--shiki-dark:#8B949E\"># --- Read the current membership ---\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">url \u003C\u002Fspan>\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\">=\u003C\u002Fspan>\u003Cspan style=\"color:#0550AE;--shiki-dark:#79C0FF\"> BASE_URL\u003C\u002Fspan>\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> \"\u002Fgroups\u002F120363041234567890@g.us\u002Fparticipants\"\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">headers \u003C\u002Fspan>\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\">=\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {\u003C\u002Fspan>\u003Cspan style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Authorization\"\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: \u003C\u002Fspan>\u003Cspan style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Bearer \"\u003C\u002Fspan>\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#0550AE;--shiki-dark:#79C0FF\"> TOKEN\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">response \u003C\u002Fspan>\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\">=\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> requests.get(url, \u003C\u002Fspan>\u003Cspan style=\"color:#953800;--shiki-dark:#FFA657\">headers\u003C\u002Fspan>\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\">=\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">headers)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#0550AE;--shiki-dark:#79C0FF\">print\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(response.json())\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6E7781;--shiki-dark:#8B949E\"># --- Add the people who are missing ---\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">url \u003C\u002Fspan>\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\">=\u003C\u002Fspan>\u003Cspan style=\"color:#0550AE;--shiki-dark:#79C0FF\"> BASE_URL\u003C\u002Fspan>\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> \"\u002Fgroups\u002F120363041234567890@g.us\u002Fparticipants\"\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">payload \u003C\u002Fspan>\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\">=\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { \u003C\u002Fspan>\u003Cspan style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"participants\"\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: [\u003C\u002Fspan>\u003Cspan style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"+15551234567\"\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, \u003C\u002Fspan>\u003Cspan style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"+15559876543\"\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">] }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">headers \u003C\u002Fspan>\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\">=\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#0A3069;--shiki-dark:#A5D6FF\">    \"Authorization\"\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: \u003C\u002Fspan>\u003Cspan style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Bearer \"\u003C\u002Fspan>\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#0550AE;--shiki-dark:#79C0FF\"> TOKEN\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#0A3069;--shiki-dark:#A5D6FF\">    \"Content-Type\"\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: \u003C\u002Fspan>\u003Cspan style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"application\u002Fjson\"\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">response \u003C\u002Fspan>\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\">=\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> requests.post(url, \u003C\u002Fspan>\u003Cspan style=\"color:#953800;--shiki-dark:#FFA657\">json\u003C\u002Fspan>\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\">=\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">payload, \u003C\u002Fspan>\u003Cspan style=\"color:#953800;--shiki-dark:#FFA657\">headers\u003C\u002Fspan>\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\">=\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">headers)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#0550AE;--shiki-dark:#79C0FF\">print\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(response.json())\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6E7781;--shiki-dark:#8B949E\"># --- Remove someone who has left the team ---\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">url \u003C\u002Fspan>\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\">=\u003C\u002Fspan>\u003Cspan style=\"color:#0550AE;--shiki-dark:#79C0FF\"> BASE_URL\u003C\u002Fspan>\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> \"\u002Fgroups\u002F120363041234567890@g.us\u002Fparticipants\u002F+15551234567\"\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">headers \u003C\u002Fspan>\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\">=\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {\u003C\u002Fspan>\u003Cspan style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Authorization\"\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: \u003C\u002Fspan>\u003Cspan style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Bearer \"\u003C\u002Fspan>\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#0550AE;--shiki-dark:#79C0FF\"> TOKEN\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">response \u003C\u002Fspan>\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\">=\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> requests.delete(url, \u003C\u002Fspan>\u003Cspan style=\"color:#953800;--shiki-dark:#FFA657\">headers\u003C\u002Fspan>\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\">=\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">headers)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#0550AE;--shiki-dark:#79C0FF\">print\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(response.status_code)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>",{"label":142,"id":36,"lang":36,"code":139,"html":154},"\u003Cpre class=\"shiki shiki-themes github-light-default github-dark-default\" style=\"background-color:#ffffff;--shiki-dark-bg:#0d1117;color:#1f2328;--shiki-dark:#e6edf3\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\">import\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> hashlib\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\">import\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> hmac\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\">import\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> os\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\">import\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> time\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\">from\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> fastapi \u003C\u002Fspan>\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\">import\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> FastAPI, Header, HTTPException, Request\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">app \u003C\u002Fspan>\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\">=\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> FastAPI()\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#0550AE;--shiki-dark:#79C0FF\">SECRET\u003C\u002Fspan>\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> os.environ[\u003C\u002Fspan>\u003Cspan style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"WAPITO_WEBHOOK_SECRET\"\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].encode()\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#0550AE;--shiki-dark:#79C0FF\">EVENTS\u003C\u002Fspan>\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> [\u003C\u002Fspan>\u003Cspan style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"groups.participants\"\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\">def\u003C\u002Fspan>\u003Cspan style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> verify\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(raw: \u003C\u002Fspan>\u003Cspan style=\"color:#0550AE;--shiki-dark:#79C0FF\">bytes\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, header: \u003C\u002Fspan>\u003Cspan style=\"color:#0550AE;--shiki-dark:#79C0FF\">str\u003C\u002Fspan>\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\"> |\u003C\u002Fspan>\u003Cspan style=\"color:#0550AE;--shiki-dark:#79C0FF\"> None\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) -> \u003C\u002Fspan>\u003Cspan style=\"color:#0550AE;--shiki-dark:#79C0FF\">bool\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#0A3069;--shiki-dark:#A5D6FF\">    \"\"\"Checks the X-Wapito-Signature header: t=&#x3C;ms>,v1=&#x3C;hex hmac-sha256>.\"\"\"\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\">    if\u003C\u002Fspan>\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\"> not\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> header:\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\">        return\u003C\u002Fspan>\u003Cspan style=\"color:#0550AE;--shiki-dark:#79C0FF\"> False\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    parts \u003C\u002Fspan>\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\">=\u003C\u002Fspan>\u003Cspan style=\"color:#0550AE;--shiki-dark:#79C0FF\"> dict\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(p.split(\u003C\u002Fspan>\u003Cspan style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"=\"\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, \u003C\u002Fspan>\u003Cspan style=\"color:#0550AE;--shiki-dark:#79C0FF\">1\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) \u003C\u002Fspan>\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\">for\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> p \u003C\u002Fspan>\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\">in\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> header.split(\u003C\u002Fspan>\u003Cspan style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\",\"\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) \u003C\u002Fspan>\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\">if\u003C\u002Fspan>\u003Cspan style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> \"=\"\u003C\u002Fspan>\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\"> in\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> p)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    ts, sig \u003C\u002Fspan>\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\">=\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> parts.get(\u003C\u002Fspan>\u003Cspan style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"t\"\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">), parts.get(\u003C\u002Fspan>\u003Cspan style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"v1\"\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\">    if\u003C\u002Fspan>\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\"> not\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> ts \u003C\u002Fspan>\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\">or\u003C\u002Fspan>\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\"> not\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> sig:\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\">        return\u003C\u002Fspan>\u003Cspan style=\"color:#0550AE;--shiki-dark:#79C0FF\"> False\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\">    if\u003C\u002Fspan>\u003Cspan style=\"color:#0550AE;--shiki-dark:#79C0FF\"> abs\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(time.time() \u003C\u002Fspan>\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\">*\u003C\u002Fspan>\u003Cspan style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 1000\u003C\u002Fspan>\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\"> -\u003C\u002Fspan>\u003Cspan style=\"color:#0550AE;--shiki-dark:#79C0FF\"> int\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(ts)) \u003C\u002Fspan>\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\">>\u003C\u002Fspan>\u003Cspan style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 300_000\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:  \u003C\u002Fspan>\u003Cspan style=\"color:#6E7781;--shiki-dark:#8B949E\"># 5 minute clock skew\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\">        return\u003C\u002Fspan>\u003Cspan style=\"color:#0550AE;--shiki-dark:#79C0FF\"> False\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    expected \u003C\u002Fspan>\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\">=\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> hmac.new(\u003C\u002Fspan>\u003Cspan style=\"color:#0550AE;--shiki-dark:#79C0FF\">SECRET\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, ts.encode() \u003C\u002Fspan>\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\">+\u003C\u002Fspan>\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\"> b\u003C\u002Fspan>\u003Cspan style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\".\"\u003C\u002Fspan>\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> raw, hashlib.sha256).hexdigest()\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\">    return\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> hmac.compare_digest(expected, sig)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#8250DF;--shiki-dark:#D2A8FF\">@app.post\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(\u003C\u002Fspan>\u003Cspan style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"\u002Fwapito\"\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\">async\u003C\u002Fspan>\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\"> def\u003C\u002Fspan>\u003Cspan style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> wapito\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(request: Request, signature: \u003C\u002Fspan>\u003Cspan style=\"color:#0550AE;--shiki-dark:#79C0FF\">str\u003C\u002Fspan>\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\"> |\u003C\u002Fspan>\u003Cspan style=\"color:#0550AE;--shiki-dark:#79C0FF\"> None\u003C\u002Fspan>\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> Header(\u003C\u002Fspan>\u003Cspan style=\"color:#953800;--shiki-dark:#FFA657\">default\u003C\u002Fspan>\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\">=\u003C\u002Fspan>\u003Cspan style=\"color:#0550AE;--shiki-dark:#79C0FF\">None\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, \u003C\u002Fspan>\u003Cspan style=\"color:#953800;--shiki-dark:#FFA657\">alias\u003C\u002Fspan>\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\">=\u003C\u002Fspan>\u003Cspan style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"X-Wapito-Signature\"\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)):\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    raw \u003C\u002Fspan>\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\">=\u003C\u002Fspan>\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\"> await\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> request.body()\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\">    if\u003C\u002Fspan>\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\"> not\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> verify(raw, signature):\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\">        raise\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> HTTPException(\u003C\u002Fspan>\u003Cspan style=\"color:#953800;--shiki-dark:#FFA657\">status_code\u003C\u002Fspan>\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\">=\u003C\u002Fspan>\u003Cspan style=\"color:#0550AE;--shiki-dark:#79C0FF\">401\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, \u003C\u002Fspan>\u003Cspan style=\"color:#953800;--shiki-dark:#FFA657\">detail\u003C\u002Fspan>\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\">=\u003C\u002Fspan>\u003Cspan style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"bad signature\"\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    payload \u003C\u002Fspan>\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\">=\u003C\u002Fspan>\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\"> await\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> request.json()\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\">    if\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> payload[\u003C\u002Fspan>\u003Cspan style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"event\"\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">] \u003C\u002Fspan>\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\">in\u003C\u002Fspan>\u003Cspan style=\"color:#0550AE;--shiki-dark:#79C0FF\"> EVENTS\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#0550AE;--shiki-dark:#79C0FF\">        print\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(payload[\u003C\u002Fspan>\u003Cspan style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"event\"\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">], payload[\u003C\u002Fspan>\u003Cspan style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"data\"\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">])\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#CF222E;--shiki-dark:#FF7B72\">    return\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {\u003C\u002Fspan>\u003Cspan style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"ok\"\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: \u003C\u002Fspan>\u003Cspan style=\"color:#0550AE;--shiki-dark:#79C0FF\">True\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}  \u003C\u002Fspan>\u003Cspan style=\"color:#6E7781;--shiki-dark:#8B949E\"># answer 2xx fast; do the real work in a queue\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>",1790464899911]