Skip to content

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>):

Terminal window
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:8000
Tunneling '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.

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:

Terminal window
python main.py # or however you normally run it - note the port it's listening on

Then, from the linked project directory (or with --bot <bot-id>):

Terminal window
pywa cloud tunnel --port 8000 # the port your bot above is listening on
Tunneling '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:8000
POST /webhook -> 200

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.

  • Opt-in, not automatic. The tunnel is only active while pywa dev --tunnel/pywa cloud tunnel is actually running - starting your bot locally on its own (plain pywa dev, or python 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 logs or any other existing command.