Short answer
The token in the request wasn’t signed by this project’s current key. Check that the Supabase URL and the API key in production come from the same project, copy the current key from Settings, then API Keys, and redeploy. If the legacy JWT secret was changed, replace the anon and service_role keys everywhere and have users sign in again.
Where the bad signature comes from
01
The URL and the key are from different projects
“Provided JWT couldn't be decoded or it is invalid.” Supabase, PostgREST error codes
A frequent mix-up: production points at one project’s URL with another project’s anon key, often a dev key left in a host’s settings. Check both values in the production environment variables, then redeploy, since front-end builds bake them in.
02
The legacy JWT secret was changed
“anon and service_role must be rotated simultaneously.” Supabase, JWT signing keys
The legacy anon and service_role keys are tokens signed with the project’s JWT secret. Changing that secret makes the old keys and every signed-in user’s token fail at once. Put the new keys in every app, server and function that used the old ones.
03
Old sessions from before the change
“Currently active users get immediately signed out.” Supabase, JWT signing keys
After a legacy secret change, browsers and phones still hold tokens signed with the old secret. Supabase’s comparison says active users are signed out. Handle a 401 by signing out and sending the user back to sign-in.
04
Your own code checks tokens with the old secret
“Make sure your app does not directly rely on the legacy JWT secret.” Supabase, JWT signing keys
If a server, Edge Function or other service verifies or creates tokens with the legacy JWT secret, moving to the new signing keys breaks it. Verify Supabase tokens with getClaims() or the project’s published keys instead.
05
A key pasted in the wrong format
“Publishable and secret keys are short strings, not JWTs.” Supabase, API keys
Supabase’s newer publishable and secret keys are short strings, not tokens. Code or a tutorial written for the old eyJ keys can end up sending one of them as the Authorization bearer token, or a pasted key picks up a stray space, quote or line break. Check that apikey and Authorization hold exactly the key from API Keys, with nothing added.
Checked against the vendor’s docs on : Supabase docs: PostgREST error codes; Supabase docs: JWT signing keys; Supabase docs: API keys.
Rather hand it over?
Fixed scope, fixed price, paid upfront. The work happens in writing; no call needed.
- from $450App Fix TicketOne reproducible bug in an existing mobile or web app, diagnosed and fixed.See scope and start
- from $450AI-Built App RescueA Lovable, Bolt or Cursor app checked end to end in 3 days, then its worst problems fixed at a fixed price.No issues worth fixing, no charge. If the Launch Check finds nothing rated medium or higher, Simon refunds the full $450. You keep the report either way.See scope and start
Related: Supabase Invalid Refresh Token logouts · Supabase anon key exposed · Supabase service role key exposed · Supabase Email address is invalid