# 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.

Page: https://sdk.chatfuel.com/docs/guides/rotate-the-token
Markdown: https://sdk.chatfuel.com/docs/guides/rotate-the-token.md

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.

<Steps>
  <Step>
    ### Generate the replacement [#generate-the-replacement]

    Open [panel.chatfuel.com/integration/auth/token](https://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.
  </Step>

  <Step>
    ### Run the wizard's `auth` command in the app [#run-the-wizards-auth-command-in-the-app]

    ```bash
    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`. Nothing else counts, and a directory without one is refused by name rather than
    half-processed. Run it from somewhere else with `--dir`:

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

    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.
  </Step>

  <Step>
    ### Know what it rewrote [#know-what-it-rewrote]

    One file, one line:

    * Every line beginning `CHATFUEL_TOKEN=` is removed, and the new value is written as the **first**
      line of the file.
    * The file is written and then `chmod`ped to `0600`, so it is readable by you and nobody else on
      the machine.
    * Everything else in `.env` 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 closing line says `Token rotated in .env`, which is precisely
    what happened.

    One edge worth knowing if your `.env` is hand-maintained. The check that decides whether this is a
    Chatfuel app accepts a commented or indented `CHATFUEL_TOKEN=` line; the rewrite removes only
    lines that begin with it exactly. So an indented `  CHATFUEL_TOKEN=<old>` survives the rotation
    and sits in the file beside the new one.
  </Step>

  <Step>
    ### Get the new value to where the proxy reads it [#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.

    <Tabs items="['Dev server', 'Your own server', 'Vercel']">
      <Tab value="Dev server">
        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.

        ```bash
        npm run dev
        ```

        Restart it. That is the whole step.
      </Tab>

      <Tab value="Your own server">
        The bundled server reads `process.env`, so the new value arrives however you deliver environment
        to that process — a restarted `npm start` reading the rewritten `.env`, a systemd unit, an
        `--env-file` on the container. Restart the process; no rebuild is needed, because the token has no
        `VITE_` prefix and was never in the client bundle.
      </Tab>

      <Tab value="Vercel">
        `.env` is never uploaded, so **rewriting it locally changes nothing on the deployment**. The
        project environment is the only source there.

        ```bash
        npm run deploy
        ```

        That pushes every variable your `.env` holds with `vercel env add --force`, to production and
        preview, overwriting rather than adding a second value — `CHATFUEL_TOKEN` among them, as a
        sensitive variable that cannot be read back out of the dashboard — and then redeploys. Re-running
        is the normal case; the project is reused and nothing is re-created.

        Setting the variable in the dashboard by hand works too, but a variable change alone does not move
        the running deployment: you still redeploy.
      </Tab>
    </Tabs>
  </Step>

  <Step>
    ### Prove the deployment is on the new token [#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. On your own server that is `/healthz`, answering `ok` and the gate's mode:

    ```json
    { "ok": true, "auth": "off" }
    ```

    On Vercel it is `/chatfuel/healthz`, and the answer carries a `problems` array as well — 200 when
    it is empty, 503 when it is not, with `ProxyTokenMissing` in it if no token reached the
    deployment at all.

    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, with a message
    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](/docs/reference/errors).
  </Step>
</Steps>

<Callout type="warn">
  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.
</Callout>

Where the token sits relative to everything else is [the token
boundary](/docs/concepts/token-boundary); every variable the app reads is on
[Environment variables](/docs/deploy/environments).
