Invalid JWT after switching to sb_publishable or sb_secret keys

The app broke after Supabase's new sb_publishable or sb_secret keys? Legacy keys can stay on until you swap them. Don't send the new keys as a Bearer JWT.

Reviewed October 14, 2026

What it means in plain English

Supabase is replacing the old anon and service_role keys with a publishable key (sb_publishable_…) and a secret key (sb_secret_…). The new keys are not JWTs. If something still treats them like the old keys, the API answers Invalid JWT or 401 and the live app looks broken.

Likely causes

  • The new key was sent on Authorization: Bearer. Supabase's migration guide says the platform then tries to parse it as a JWT and rejects it with Invalid JWT. Send it on the apikey header.
  • An Edge Function still has verify_jwt on. That check only understands the old JWT-based keys.
  • Edge Function code still reads SUPABASE_ANON_KEY or SUPABASE_SERVICE_ROLE_KEY. The new values are JSON objects in SUPABASE_PUBLISHABLE_KEYS and SUPABASE_SECRET_KEYS.
  • A database webhook or pg_net call still puts the key on Authorization.
  • Legacy keys were turned off before every client, script, and webhook had moved.

How to fix it, step by step

  1. In the dashboard open Settings → API Keys and the Publishable and secret API keys tab. If you see Create new API keys, creating them is safe: the old anon and service_role keys keep working beside them.
  2. In browser code, replace the anon key with the publishable key. In server code, replace service_role with the secret key. The publishable key has the same low privileges as anon. The secret key bypasses Row Level Security. Never put sb_secret_… in frontend code or in a VITE_ / NEXT_PUBLIC_ variable.
  3. Send the new keys on the apikey header only. For an Edge Function, set verify_jwt = false in supabase/config.toml and check the caller in your own code, or use withSupabase from npm:@supabase/server@1 as the migration guide shows.
  4. In an Edge Function, read the key by name. The default key is named default: JSON.parse(Deno.env.get('SUPABASE_SECRET_KEYS'))['default']. Confirm those secrets exist under Edge Functions → Secrets.
  5. For a dashboard webhook or pg_net call, remove the Authorization header that holds the key and send apikey instead. Don't paste the secret into SQL; the guide says to store it in Vault.
  6. Only after every app, mobile build, CI job, and webhook uses the new keys, deactivate the legacy keys on the same Settings → API Keys page. You can turn them back on if you missed a client.

How to check it worked

  • A request from the live site returns data or a normal auth response, not Invalid JWT.
  • The browser still uses only the publishable key. The secret key appears only in server logs or server env vars.

Sources

Can't fix it? Get help.

If the steps didn't clear the error, send what the screen says. A person can take a look. This is a request for a paid fix, not an instant chat.

Request a fix