Local Development
Normally, developing against real WhatsApp webhooks means running a separate tunnel tool (ngrok or
similar) and manually repointing the callback URL in the Meta App dashboard for every session -
then pointing it back at the deployed container afterward. Pywa Cloud’s tunnel does this without
either step: your bot already has one stable public webhook URL
(https://<your control plane>/webhook/<slug>, registered once when you
connect a WhatsApp number) - the tunnel just changes where Pywa
Cloud forwards traffic arriving at that URL, from the deployed container to your own machine. The
URL registered with Meta never has to change between local development and production.
This relays webhook traffic only - it doesn’t fetch or inject your bot’s real WhatsApp credentials
into your environment. pywa dev (below) still runs against whatever PYWA_TOKEN/PYWA_PHONE_ID/
etc. your own project already provides (a local .env, hardcoded values, or however you normally
configure it for local runs).
The recommended way is pywa dev --tunnel (pywa’s own local dev server, with the tunnel wired
in), from the linked project directory (or with --bot <bot-id>):
pywa dev --tunnel # runs your bot locally AND relays this bot's webhook traffic here🚀 Starting Pywa in development mode🌐 Server URL: http://127.0.0.1:8000Tunneling 'my-bot''s webhook traffic to http://127.0.0.1:8000 ...--tunnel picks up --port/--bot the same way the rest of pywa dev does - --bot <bot-id>
if the directory isn’t linked to one already.
Standalone tunnel
Section titled “Standalone tunnel”If you’re not using pywa dev - a different framework runner, or you’d rather run your bot and
the tunnel as two separate steps - run your bot locally first, exactly as you would with any other
Python HTTP server:
python main.py # or however you normally run it - note the port it's listening onThen, from the linked project directory (or with --bot <bot-id>):
pywa cloud tunnel --port 8000 # the port your bot above is listening onTunneling 'my-bot''s webhook traffic to http://127.0.0.1:8000 ...Press Ctrl+C to stop and hand control back to the deployed container.
Connected - forwarding webhook traffic to http://127.0.0.1:8000POST /webhook -> 200What happens either way
Section titled “What happens either way”From this point, every webhook Meta sends for this bot arrives at your local process instead of the deployed one - signature-validated exactly the same way it would be in production (validation happens once, at Pywa Cloud’s ingress, before the tunnel/container fork - what changes is only where an already-validated request goes). Ctrl+C (or closing your laptop, losing WiFi, anything that drops the connection) immediately hands control back to the deployed container - nothing about your bot’s deployment or its Meta configuration needs to change either way, and the container is never stopped while a tunnel is connected, only temporarily not receiving traffic.
A few things worth knowing
Section titled “A few things worth knowing”- Opt-in, not automatic. The tunnel is only active while
pywa dev --tunnel/pywa cloud tunnelis actually running - starting your bot locally on its own (plainpywa dev, orpython main.py) doesn’t reroute anything. - One tunnel per bot at a time. Starting another tunnel for the same bot (e.g. a teammate starts one while yours is still connected) takes over - your own connection is closed with a clear reason, not silently starved of traffic with no explanation.
- No request buffering across a reconnect. A dropped connection doesn’t queue up in-flight webhook deliveries to replay once you’re back - Meta already retries failed webhook deliveries, which is a simpler and more robust safety net than building that here.
- This is a webhook relay, not a general tunnel. It doesn’t expose anything beyond the webhook
path, and doesn’t replace
pywa cloud logsor any other existing command.