@open-wa/integration-cloudflare
Cloudflare Tunnel integration plugin for open-wa
@open-wa/cf-proxy
@open-wa/cf-proxy is the Cloudflare Worker reverse tunnel for open-wa Easy API sessions. The local runtime opens an outbound WebSocket to the Worker, so the local machine does not need an inbound port. The Worker remains a remotely reachable authenticated entry point.
The package is private to the open-wa monorepo. It is not an npm consumer package, so deployment starts from this repository's packages/cf-proxy entrypoint.
Topology and authentication
Remote consumer --HTTPS/WebSocket--> Cloudflare Worker --Durable Object--> local TunnelClient --HTTP--> Easy APIThe Worker uses two independent secrets:
UPSTREAM_TOKENauthenticates the local runtime at/sessions/:sessionId/upstream.CONSUMER_TOKENauthenticates remote callers on other session paths.
The Worker routes each session ID to a Durable Object. An absent upstream returns 503 Session offline; a request that waits more than 30 seconds returns 504 Gateway Timeout from session upstream. Hibernation can reduce active Durable Object compute while the connection is idle, but it does not make Worker requests, storage, egress, domains, or account-plan charges free. Check Cloudflare Workers pricing for the account and plan in use.
Deploy the Worker
From a clean checkout:
git clone https://github.com/open-wa/wa-automate-nodejs.git
cd wa-automate-nodejs
corepack enable
pnpm install
cd packages/cf-proxy
pnpm exec wrangler login
pnpm exec wrangler whoamiwrangler.jsonc binds the SESSION_TUNNEL Durable Object to SessionTunnel. Set the secret values through Wrangler without committing them:
export OPENWA_PROXY_UPSTREAM_TOKEN="$(openssl rand -hex 32)"
export OPENWA_PROXY_CONSUMER_TOKEN="$(openssl rand -hex 32)"
printf '%s' "$OPENWA_PROXY_UPSTREAM_TOKEN" | pnpm exec wrangler secret put UPSTREAM_TOKEN
printf '%s' "$OPENWA_PROXY_CONSUMER_TOKEN" | pnpm exec wrangler secret put CONSUMER_TOKEN
pnpm exec wrangler deployRecord the deployed hostname as OPENWA_PROXY_HOST, for example https://open-wa-proxy.<account>.workers.dev. The local runtime needs the upstream value; remote callers need the consumer value.
Contributors with an existing checkout can run pnpm install from the repository root and use pnpm --filter @open-wa/cf-proxy exec wrangler whoami followed by pnpm --filter @open-wa/cf-proxy exec wrangler deploy. Run secret put again only when rotating a value.
Attach a local session
Run the CLI on the machine that owns the WhatsApp browser session:
export OPENWA_PROXY_HOST='https://open-wa-proxy.<account>.workers.dev'
npx @open-wa/wa-automate@5.1.0 \
--session-id sales \
--port 8080 \
--proxy-host "$OPENWA_PROXY_HOST" \
--proxy-token "$OPENWA_PROXY_UPSTREAM_TOKEN"The CLI converts the HTTPS host to a WebSocket host and connects to /sessions/sales/upstream. WhatsApp Web and Easy API stay on the local machine; the Worker does not run the browser or change session readiness.
Consume a session
Use the consumer token in an Authorization: Bearer header rather than a URL query when making HTTP calls:
curl -i \
"$OPENWA_PROXY_HOST/sessions/sales/api/getHostNumber" \
-H "Authorization: Bearer $OPENWA_PROXY_CONSUMER_TOKEN"For a command that is protected by the local Easy API key, send both credentials:
curl -i -X POST \
"$OPENWA_PROXY_HOST/sessions/sales/api/sendText" \
-H "Authorization: Bearer $OPENWA_PROXY_CONSUMER_TOKEN" \
-H "X-API-Key: $OPENWA_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"args":["1234567890@c.us","Hello through Cloudflare"]}'Node clients can use the compatibility URL. The client converts it to the tunnel WebSocket and sends the consumer token to /sessions/<sessionId>/connect:
import { SocketClient } from '@open-wa/socket-client';
const client = await SocketClient.connect(
`cf-proxy://open-wa-proxy.<account>.workers.dev?sessionId=sales&token=${encodeURIComponent(process.env.OPENWA_PROXY_CONSUMER_TOKEN)}`,
);
console.log(await client.getHostNumber());The remote caller still receives the local Easy API's authentication or readiness response. A valid consumer token does not replace the local X-API-Key.
Troubleshooting
| Symptom | Command or observation | Expected result | Next action |
|---|---|---|---|
| Worker deploy fails | pnpm exec wrangler whoami and inspect wrangler.jsonc | The intended account is authenticated and SESSION_TUNNEL binds SessionTunnel. | Log in to the intended account and deploy from packages/cf-proxy; fix binding or migration data before retrying. |
Local runtime gets 401 Unauthorized Upstream | Compare --proxy-token with pnpm exec wrangler secret list. | The local runtime connects to the upstream WebSocket for the same session ID. | Rotate UPSTREAM_TOKEN, pass the exact value to the CLI, and restart it. |
Consumer gets 401 Unauthorized Consumer | Repeat the HTTP request with the Authorization header above. | A matching consumer token reaches the Worker. | Rotate CONSUMER_TOKEN if necessary and reconnect callers. |
Consumer gets 503 Session offline | Inspect the local runtime log for the upstream connection and match the session ID in the URL. | The upstream WebSocket is open for that session. | Start the local runtime or correct --session-id; then check local Easy API readiness. |
Consumer gets 504 | Call the same API path against http://127.0.0.1:8080. | The local API completes within 30 seconds. | Fix the local API, browser, or Easy API key before investigating Worker routing. |
Development
pnpm --filter @open-wa/cf-proxy devThe Worker entrypoint is packages/cf-proxy/src/index.ts; the Durable Object is src/tunnel-do.ts.
Documentation
See the Cloudflare Session Proxy guide.
License
H-DNH 1.1 - Hippocratic + Do Not Harm
Was this helpful?
Your answer includes the page path and docs version.
