Skip to content
ChatfuelSDK
Concepts

The token boundary

A Chatfuel token can read and change every bot in an account, so it never leaves the server — and everything about the proxy follows from that.

A Chatfuel token can read and change every bot in an account. There is no read-only token and no per-bot token, so a token in a browser is an account in a browser.

It therefore never gets there. Every call the app makes goes to its own origin under /chatfuel/*, and a proxy running on the server side attaches the token. The client in the browser is constructed without one — there is no code path that could send it, because it has nothing to send.

One proxy, three hosts

The proxy is one source tree with three ways to run it, which is why the gate, the fences and the error codes are identical wherever you deploy.

WhereWhat runs it
Developmenta Vite plugin, inside the dev server
Your own serverserver/entry.ts, a Node HTTP server that also serves the built client
Vercelapi/chatfuel.ts, a serverless function behind a rewrite

All three mount the same routes: a GraphQL POST, a WebSocket on the same path, and a REST passthrough for file uploads.

What the proxy strips, and what it adds

The outgoing request is built from scratch rather than copied and amended. That is the whole mechanism: the browser's Authorization header and its cookies are not stripped by a filter that someone could forget to update — they are never in the object that goes upstream at all. The only thing added is the token.

Nothing prints it, either. The master token is never logged, and the one outbound-proxy string that does reach a log line has its password replaced first.

A missing token is a configuration problem, not a crash: the proxy answers with a synthetic ProxyTokenMissing envelope rather than taking the dev server down with it.

The WebSocket is relayed, not forwarded

Subscriptions cannot be a tunnel, because a tunnel would carry whatever the browser sent as its credentials. So the relay reads frames. It accepts the subprotocol, waits for the browser's connection_init, decides whether that connection may proceed, and only then opens the upstream socket and sends its own connection_init carrying the token. Every other frame passes through verbatim in both directions.

The consequence worth knowing: an unauthenticated socket never touches Chatfuel at all. When the upstream socket does drop, the browser's socket is closed too and the client reconnects through a fresh relay.

Anything named VITE_* is compiled into the browser bundle at build time. Anything else is read by the proxy at runtime. A secret with a VITE_ prefix is not a secret — which is why the token, the Supabase service-role key and the admin password do not have one. The full list of variables and which side reads each is in environment variables.

What it costs

Two things.

The hop is real: every call is your origin, then Chatfuel. On Vercel that hop is a function with a duration limit, which is what bounds how long a subscription's socket lives — see Vercel.

And the proxy is code in your project, not a service we run. The whole tree is copied in under vendor/chatfuel-proxy/, and your app declares ws, undici and https-proxy-agent as runtime dependencies because of it. If you delete it, you have nothing to talk to Chatfuel with.

With the auth module installed the same proxy takes on a second job — deciding whose request this is, not only where it goes. That is the auth gate.

On this page