[{"data":1,"prerenderedAt":155},["ShallowReactive",2],{"pseo:tutorials:connect-number-qr-pairing:python":3,"highlight:tutorial:connect-number-qr-pairing:python":144},{"ban_risk":4,"ban_risk_note":5,"deploy_targets":6,"errors":11,"faqs":21,"feature":32,"full_script":36,"gotchas":40,"intro_note":46,"language":47,"pitfalls":51,"prerequisites":55,"related":58,"seo":85,"siblings":101,"step_summary":108,"steps":109,"tier":137,"url":95,"webhook_receiver":138},"medium","Linking a device is normal; what you do afterwards decides the risk. A brand-new number that starts sending at volume on its first day is the classic ban profile, which is why Wapito counts a warm-up ladder from the moment a number first connects and enforces it on every plan. Link the number, let it hold real conversations, then automate.",[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,18],{"title":13,"url":14},"unauthorized (401)","\u002Fdocs\u002Ferrors\u002Funauthorized\u002F",{"title":16,"url":17},"token_revoked (401)","\u002Fdocs\u002Ferrors\u002Ftoken-revoked\u002F",{"title":19,"url":20},"channel_locked (403)","\u002Fdocs\u002Ferrors\u002Fchannel-locked\u002F",[22,26,29],{"a":23,"q":24,"source":25},"A pairing code, in almost every case. It can be read aloud, pasted into a chat or typed from a support ticket, and it does not depend on a camera pointed at a refreshing image. QR linking of new devices has also been unreliable across engine libraries since mid-2026, so treat it as the fallback.","Should I use a pairing code or a QR?","features.json",{"a":27,"q":28,"source":25},"Yes - that is the central difference from the official platform. Wapito links a companion device, exactly like WhatsApp Web, so the human keeps their app, their chats and their history while your automation works alongside them on the same number.","Can I keep using WhatsApp on the phone afterwards?",{"a":30,"q":31,"source":25},"The channel webhook fires with the new state, the previous state and a reason. A transient engine restart reconnects by itself; a logout by the phone's owner or a ban does not, and both need a person. Branch on the reason rather than retrying blindly into a session that is gone.","What happens if the session drops?",{"name":33,"slug":34,"url":35},"Connect a Number","connect-number-qr-pairing","\u002Fwhatsapp-api\u002Fconnect-number-qr-pairing\u002F",{"code":37,"filename":38,"lang":39},"\"\"\"Connect a Number with the Wapito WhatsApp API.\n\nRead the state, request a pairing code, use the QR only as a fallback, and drive everything else from the channel webhook.\n\nRun it with:\n    export WAPITO_TOKEN=\"wpt_...\"\n    python connect-number-qr-pairing.py\n\"\"\"\nimport os\n\nimport requests\n\nBASE_URL = \"https:\u002F\u002Fapi.wapito.com\u002Fv1\"\nTOKEN = os.environ[\"WAPITO_TOKEN\"]\n\n# --- Check the channel state ---\nurl = BASE_URL + \"\u002Fchannel\"\n\nheaders = {\"Authorization\": \"Bearer \" + TOKEN}\n\nresponse = requests.get(url, headers=headers)\n\nprint(response.json())\n\n# --- Request a pairing code ---\nurl = BASE_URL + \"\u002Fchannel\u002Fpairing-code\"\n\npayload = { \"phone\": \"+15557654321\" }\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# --- Fall back to a QR if you need to ---\nurl = BASE_URL + \"\u002Fchannel\u002Fqr\"\n\nheaders = {\"Authorization\": \"Bearer \" + TOKEN}\n\nresponse = requests.get(url, headers=headers)\n\nprint(response.json())\n\n","connect-number-qr-pairing.py","python",[41,42,43,44,45],"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\".","Connecting a number from Python is a state machine driven by requests and a FastAPI route: read the channel state, ask for a pairing code while the person is on the phone, stream the QR only as a fallback, and let the channel event decide what happens next. A small loop with time.sleep polls the state; the webhook does the rest.",{"family":48,"name":49,"slug":39,"url":50},"scripting","Python","\u002Fwhatsapp-bot\u002Fpython\u002F",[52,53,54],"A poll loop with no attempt limit runs forever when the person never types the code; count iterations and stop with a clear message so the worker is not stuck.","Printing the QR to a terminal with a library that renders it as ASCII works locally and fails on a server with no TTY; stream it to a browser instead.","Catching Exception around the state read hides the 403 channel_locked that means the plan gate is on, and the loop polls a channel that will never connect; catch requests.HTTPError and inspect the code.",[56,57],"pip install requests","os.environ[\"WAPITO_TOKEN\"]",{"language_hub":50,"operations":59,"pillar":35,"same_language_features":69},[60,63,66],{"title":61,"url":62},"GET \u002Fchannel","\u002Fdocs\u002Fapi\u002Fchannel\u002Fget-channel\u002F",{"title":64,"url":65},"POST \u002Fchannel\u002Fpairing-code","\u002Fdocs\u002Fapi\u002Fchannel\u002Fcreate-pairing-code\u002F",{"title":67,"url":68},"GET \u002Fchannel\u002Fqr","\u002Fdocs\u002Fapi\u002Fchannel\u002Fget-channel-qr\u002F",[70,73,76,79,82],{"title":71,"url":72},"Create Group","\u002Fwhatsapp-api\u002Fcreate-group\u002Fpython\u002F",{"title":74,"url":75},"Group Participants","\u002Fwhatsapp-api\u002Fgroup-participants\u002Fpython\u002F",{"title":77,"url":78},"Group Admins","\u002Fwhatsapp-api\u002Fgroup-admins\u002Fpython\u002F",{"title":80,"url":81},"Group Invite Link","\u002Fwhatsapp-api\u002Fgroup-invite-link\u002Fpython\u002F",{"title":83,"url":84},"Group Settings","\u002Fwhatsapp-api\u002Fgroup-settings\u002Fpython\u002F",{"breadcrumb":86,"canonical":96,"description":97,"h1":98,"lastmod":99,"title":100},[87,90,93,94],{"name":88,"url":89},"Home","\u002F",{"name":91,"url":92},"WhatsApp API","\u002Fwhatsapp-api\u002F",{"name":33,"url":35},{"name":49,"url":95},"\u002Fwhatsapp-api\u002Fconnect-number-qr-pairing\u002Fpython\u002F","https:\u002F\u002Fwapito.com\u002Fwhatsapp-api\u002Fconnect-number-qr-pairing\u002Fpython\u002F","How to link a number in Python with the Wapito WhatsApp API. 4 steps with runnable code, a FastAPI webhook receiver and the errors to expect.","How to link a number in Python with the Wapito WhatsApp API","2026-09-16","Link a Number in Python (WhatsApp API Tutorial) | Wapito",[102,105],{"title":103,"url":104},"Node.js","\u002Fwhatsapp-api\u002Fconnect-number-qr-pairing\u002Fnodejs\u002F",{"title":106,"url":107},"PHP","\u002Fwhatsapp-api\u002Fconnect-number-qr-pairing\u002Fphp\u002F","Read the state, request a pairing code, use the QR only as a fallback, and drive everything else from the channel webhook.",[110,118,125,132],{"body":111,"note":112,"operationId":113,"operation_url":62,"snippet":114,"title":116,"webhook":117},"Read the state before you ask for anything. A code can only be issued while the session is waiting to be linked, so polling for one against a connected channel just produces errors.","In Python read the state with requests.get on \u002Fchannel and branch on response.json()[\"state\"] before asking for anything; a code can only be issued in the waiting state. Wrap it in a small poll loop with time.sleep(2) that stops when the state is connected or the attempt count runs out.","getChannel",{"code":115,"lang":39},"import requests\n\nurl = \"https:\u002F\u002Fapi.wapito.com\u002Fv1\u002Fchannel\"\n\nheaders = {\"Authorization\": \"Bearer wpt_YOUR_TOKEN\"}\n\nresponse = requests.get(url, headers=headers)\n\nprint(response.json())","Check the channel state",null,{"body":119,"note":120,"operationId":121,"operation_url":65,"snippet":122,"title":124,"webhook":117},"Ask for a code for the number you intend to link, and have the person type it into the linked-devices screen on that phone. Codes expire quickly, so request one while they are already looking at the phone.","In Python request a code with requests.post(url, json={\"phone\": e164}) and print it immediately for the person to type; the code expires fast, so the call belongs inside the same interactive step, not in a setup script that ran earlier. Catch requests.HTTPError for a 409 channel_not_in_qr_state and re-read the state.","createPairingCode",{"code":123,"lang":39},"import requests\n\nurl = \"https:\u002F\u002Fapi.wapito.com\u002Fv1\u002Fchannel\u002Fpairing-code\"\n\npayload = { \"phone\": \"+15557654321\" }\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())","Request a pairing code",{"body":126,"note":127,"operationId":128,"operation_url":68,"snippet":129,"title":131,"webhook":117},"The QR refreshes every few seconds, so stream it to the browser rather than emailing a screenshot. New-device QR linking has been unreliable across engines since mid-2026, which is why the code is the primary path.","In Python fetch the QR with requests.get on \u002Fchannel\u002Fqr and hand the image data to a browser through a streaming endpoint or a websocket, refreshing every few seconds; do not write it to a file and email it. A 409 means the channel left the waiting state while you were fetching.","getChannelQr",{"code":130,"lang":39},"import requests\n\nurl = \"https:\u002F\u002Fapi.wapito.com\u002Fv1\u002Fchannel\u002Fqr\"\n\nheaders = {\"Authorization\": \"Bearer wpt_YOUR_TOKEN\"}\n\nresponse = requests.get(url, headers=headers)\n\nprint(response.json())","Fall back to a QR if you need to",{"body":133,"note":134,"operationId":117,"operation_url":117,"snippet":117,"title":135,"webhook":136},"Every state change arrives as an event carrying the new state, the previous one and a reason. A ban needs a human and a restart does not, so branch on the reason rather than treating every disconnect the same.","In Python the FastAPI handler receives channel events with the new state, the previous one and a reason; branch on the reason string, because a ban needs a person and a restart does not. Write the transition to your database from a background task and return the 2xx.","Watch the connection over the webhook","channel","t2",{"code":139,"events":140,"filename":141,"framework":142,"install":143,"lang":39},"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 = [\"channel\"]\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":49,"id":39,"lang":39,"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\u002Fchannel\"\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":49,"id":39,"lang":39,"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\u002Fchannel\u002Fpairing-code\"\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\">\"phone\"\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: \u003C\u002Fspan>\u003Cspan style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"+15557654321\"\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":49,"id":39,"lang":39,"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\u002Fchannel\u002Fqr\"\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":49,"id":39,"lang":39,"code":37,"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\">\"\"\"Connect a Number 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\">Read the state, request a pairing code, use the QR only as a fallback, and drive everything else from the channel 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 connect-number-qr-pairing.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\"># --- Check the channel state ---\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\"> \"\u002Fchannel\"\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\"># --- Request a pairing code ---\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\"> \"\u002Fchannel\u002Fpairing-code\"\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\">\"phone\"\u003C\u002Fspan>\u003Cspan style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: \u003C\u002Fspan>\u003Cspan style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"+15557654321\"\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\"># --- Fall back to a QR if you need to ---\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\"> \"\u002Fchannel\u002Fqr\"\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\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>",{"label":142,"id":39,"lang":39,"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\">\"channel\"\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>",1790464903731]