Skip to content
ChatfuelSDK
Guides

Replace the Chatfuel token

npx @chatfuel/wizard auth rewrites one line of one file. The rest of the job is getting that value to wherever the proxy actually reads it.

A Chatfuel token can read and change every bot in the account it belongs to. Not one bot, not the bots your app happens to show — all of them. That is why it never reaches the browser, and it is also why replacing it is a real remedy rather than a gesture: there is one value, in one place, and swapping it moves the whole deployment at once.

Rotation itself is Chatfuel's, not ours. You generate a new token on the token page; the wizard's job is the second half, putting it into the app without your editing a secret by hand.

Generate the replacement

Open panel.chatfuel.com/integration/auth/token as the account owner and generate a token. Copy it and keep the tab open — nothing checks it until the next step.

Do not assume a shape or a lifetime. The token page decides both, and the wizard rejects only what cannot be a token at all: an empty string, or one with whitespace in it. Everything else goes to the API to be judged.

Run the wizard's auth command in the app

cd my-app
npx @chatfuel/wizard auth

It identifies the app by the very line it is about to rewrite — a .env that declares CHATFUEL_TOKEN, set or left empty. Nothing else counts, and a directory without one is refused by name rather than half-processed. Run it from somewhere else with --dir:

npx @chatfuel/wizard auth --dir ./my-app

A .env that is a symlink is refused here too. The token is written in place, and a link would put it wherever the link points, with the 0600 landing on the target instead of on the file you are looking at.

Then it asks for the token, masked, and checks it against the API before writing anything — currentUser, one request. A token the API rejects is re-asked rather than ending the run, three times; a network failure is not, because it would fail the same way again.

Two ways round the prompt. CHATFUEL_TOKEN in the environment is used on the first attempt instead of asking. And under --yes, or with no terminal attached, there is no prompt at all: the environment is the only source, and a token that is missing or rejected stops the run with a sentence. That last part matters more than it looks — the prompt library exits the process on end-of-input, so a scripted run that fell through to a question nobody could answer would report success and change nothing.

One question can still come back, after the token: whether .env may be added to .gitignore. It is the same guard the scaffold puts in front of the same write, asked because auth reaches apps the scaffold never touched — a clone, or a checkout whose ignore file somebody edited. Say no and nothing is written at all.

Know what it rewrote

One file, one line:

  • .env is re-read at the moment of the write, so anything you added while the API call was in flight survives.
  • Every CHATFUEL_TOKEN= line goes — plain, indented, or commented out — and the new value is written as the first line of the file.
  • The file is opened with O_NOFOLLOW and truncated in one operation, and its mode is 0600, so it is readable by you and nobody else on the machine.
  • Everything else keeps its value and its order — the Supabase keys, the workspace ids, the admin password, your own additions.

And that is the list. It touches no other file: not index.html, not the modules, not your Supabase project, not Vercel. The one exception is bookkeeping — if the guard above appended a .gitignore line, .chatfuel/lock.json is amended to record it, so the next update reads that line as the wizard's rather than as an edit of yours. The closing line says Token rotated in .env, which is precisely what happened.

Get the new value to where the proxy reads it

The proxy resolves the token once, at startup, out of whatever environment its host hands it. So the file is never the thing that matters — the host is.

The Vite plugin builds its environment bag once, when the config resolves: .env first, then process.env on top. A running dev server is still holding the old token.

npm run dev

Restart it. That is the whole step.

Prove the deployment is on the new token

The wizard has already proved the token itself — it printed the account it authenticated as before it wrote anything. What is left is proving the deployment picked it up.

Ask the health route. It is /chatfuel/healthz on every host — the dev server, the bundled server, the Vercel function — and the body is one field:

{ "ok": true }

200 when the proxy came up with no configuration problems, 503 when it did not. A deployment that received no token at all is one of those problems, so it answers 503. What the problem is stays off this route, which anyone who can reach the deployment can call; the startup log carries the line that names it, along with the gate's mode and every fence in force.

Either way that confirms the proxy has a token. It does not confirm it is the new one: the health route makes no Chatfuel call. So open the app and load a screen that reads real data — contacts, the inbox, a flow. Data on screen is the proof.

The two failures read differently, and it is worth knowing which you are looking at. An absent token is ProxyTokenMissing, a 500 from the proxy before anything reaches Chatfuel, naming the variable and never its value. A token Chatfuel rejects is an Unauthorized code inside an otherwise ordinary response, on every operation, and the API client never retries it. Both are in Errors.

On Vercel, the old token stays live in the deployment until you push the new one, and nothing tells you. The rotation succeeds, the wizard says Token rotated in .env, and it is telling the truth about the only file it touched — while the running app keeps using the value in the project environment. If you rotated because a token leaked, the rotation is not finished until npm run deploy has run.

Where the token sits relative to everything else is the token boundary; every variable the app reads is on Environment variables.

On this page