Skip to content

WhatsApp Flows

A WhatsApp Flow that exchanges data with your own server (rather than being purely static) needs an Endpoint URI - a URL Meta calls directly whenever a user interacts with it. On Pywa Cloud, that’s just another path under your bot’s existing webhook URL - there’s nothing extra to configure on the platform side.

Your bot’s webhook already lives at {PUBLIC_URL}/webhook/<your-bot-slug> (see Connecting a WhatsApp Number). A Flow’s Endpoint URI is any path under that same prefix - pick whatever suits your bot, e.g.:

https://<your control plane>/webhook/<your-bot-slug>/feedback_flow

Register exactly that as the Flow’s Endpoint URI in Meta’s Flow builder, and use the same path (without the /webhook/<slug> prefix - Pywa Cloud strips it before forwarding) in your own code:

import os
from pywa import WhatsApp, types
wa = WhatsApp(
phone_id=os.environ["PYWA_PHONE_ID"],
token=os.environ["PYWA_TOKEN"],
verify_token=os.environ["PYWA_VERIFY_TOKEN"],
business_private_key=os.environ["FLOW_PRIVATE_KEY"],
)
@wa.on_flow_request("/feedback_flow")
def feedback_handler(_: WhatsApp, req: types.FlowRequest): ...
@feedback_handler.on_init
def on_init(_: WhatsApp, req: types.FlowRequest):
return req.respond(screen="SURVEY")
@feedback_handler.on_data_exchange(screen="SURVEY")
def on_survey(_: WhatsApp, req: types.FlowRequest):
return req.respond(screen="THANKS", data={"message": "Thanks for your feedback!"})

That’s the whole platform-side story - Pywa Cloud’s ingress forwards POST /webhook/<slug>/feedback_flow to your bot as POST /feedback_flow, exactly matching what on_flow_request("/feedback_flow") registered, whether your bot is deployed or you’re iterating locally with pywa dev --tunnel - the routing is identical either way.

business_private_key above is not something Pywa Cloud generates, stores, or ever sees - you generate an RSA key pair yourself, keep the private key as a normal (secret) environment variable (pywa cloud env set FLOW_PRIVATE_KEY "$(cat private.pem)" --secret), and register the public half with Meta yourself, using the token Pywa Cloud already injects:

wa.set_business_public_key(public_key=open("public.pem").read())

A Flow request that reaches your code has already passed two separate checks, and both have to be right:

  1. Signature validation, at Pywa Cloud’s ingress - Meta signs every Endpoint URI request with X-Hub-Signature-256, exactly like your regular webhook traffic. This is verified before the request is ever forwarded to your bot, using the same WABA connection your bot is already assigned - nothing to configure here.
  2. Payload decryption, in your own code - the request body itself is separately encrypted with your business_private_key; pywa decrypts it automatically once you provide the key. If decryption fails, pywa (not Pywa Cloud) returns the “signal that decryption failed” response Meta’s Flow client expects - check your business_private_key first if you see this.