Short answer: some part of your app is still sending a key that no longer works, or is sending a new key in the wrong place. There are usually three places to check: the frontend (the key baked into the published site), your Edge Functions, and anything outside Supabase that calls it (Stripe webhooks, a server on Railway, Vercel or similar). Supabase lets the new keys and the old keys run side by side, so the safest fix is to put the new keys everywhere first, check nothing still uses the old ones, and only then switch the old ones off.
You might see:
| What you see | Where | Most likely cause |
|---|---|---|
Invalid API key | Browser Network tab, on a /rest/v1/ or /auth/v1/ request | The published site still has the old or disabled key |
| Everyone suddenly logged out, sign-in fails | Login page | The legacy JWT secret was rotated or revoked, so old sessions and the old anon key stopped working |
Invalid JWT or 401 from an Edge Function | /functions/v1/... | A new sb_publishable_ or sb_secret_ key is being sent as Authorization: Bearer, or the function's built-in JWT check doesn't understand the new keys |
| Webhook or server job fails with 401 | Stripe dashboard, server logs | That server still has the old service_role key, or the new secret key was copied wrong |
Why this is happening a lot right now
Two things landed at once:
- Railway's security incident (21 Oct 2026). Railway told all users to rotate every environment variable, API key and credential (Railway's disclosure). Lots of people rotated their Supabase keys in a hurry, and some revoked the old ones before the new ones were in place.
- Supabase is moving off the legacy keys. The old
anonandservice_rolekeys (they start witheyJ) are being replaced by a publishable key (sb_publishable_...) and a secret key (sb_secret_...). Supabase's guide is here: Migrating to publishable and secret API keys.
Before you change anything else: check status.supabase.com. If Supabase itself is having an incident, your new keys may be fine and you'll just make things worse by rotating again.
Step 1: find out which key the live site is sending (2 minutes)
- Open your published site in a private window.
- Press F12 (Cmd + Option + I on a Mac) and click Network.
- Reload and click any request to
.supabase.co. - Under Request Headers, look at
apikey.
- Starts with
eyJ: it's a legacy key. If you disabled the legacy keys or revoked the old JWT secret, this is your problem. - Starts with
sb_publishable_: good, that's the new browser key. If it still fails, check it matches the one in your dashboard exactly (a missing character is common). - Starts with
sb_secret_: stop. The secret key must never be in the browser. Remove it from the frontend and create a new secret key (see Step 4).
Step 2: fix the frontend key and republish
In a Lovable app the browser key usually lives in one of two places:
src/integrations/supabase/client.ts(sometimes written straight into the file), or- an environment variable such as
VITE_SUPABASE_PUBLISHABLE_KEYorVITE_SUPABASE_ANON_KEY, set in Lovable or in your host (Vercel, Netlify and so on).
Put the new publishable key there. Then republish. Anything starting with VITE_ is baked into the site when it's built, so changing the value does nothing until you rebuild and publish again. If you host outside Lovable, redeploy there too.
Step 3: fix your Edge Functions
Two separate things break here.
The key the function reads. Supabase gives Edge Functions the old keys as SUPABASE_ANON_KEY and SUPABASE_SERVICE_ROLE_KEY, and the new ones as SUPABASE_PUBLISHABLE_KEYS and SUPABASE_SECRET_KEYS. The new ones hold a small JSON object, not a plain string, so the code changes from:
const secretKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!to:
const secretKey = JSON.parse(Deno.env.get('SUPABASE_SECRET_KEYS')!)['default'](default is the name Supabase gives the first key you create.)
The header the key travels in. The new keys are not JWTs. Supabase's docs say to send them in the apikey header only. If you also send one as Authorization: Bearer ..., the platform tries to read it as a JWT and you get Invalid JWT. For functions that are called with an API key rather than a signed-in user (webhooks, server-to-server calls), Supabase says to turn off the function's built-in JWT check and check the key in your own code. Details: Authorization headers and Edge Function 401 error response.
If you add your own secrets in Lovable, users report Lovable rejects secret names starting with SUPABASE_ (source). Give your own secrets a different name, such as SB_SECRET_KEY, and read that name in your function.
Step 4: fix everything outside Supabase
Make a list of every place that talks to Supabase with the secret or service_role key: Stripe webhook handlers, cron jobs, a backend on Railway, Render or Vercel, Zapier or Make. Put the new secret key in each, then redeploy each one. Railway says services need a redeploy to pick up new values (Railway's follow-up).
The secret key bypasses Row Level Security, so it only ever goes on a server. Never in a VITE_ variable, never in the browser.
Step 5: only now switch the old keys off
In the Supabase dashboard, go to Settings > API Keys. Supabase shows when each legacy key was last used. When the old keys show no recent use, deactivate them. If something breaks, you can turn them back on while you find the straggler.
Be careful with the legacy JWT secret. Rotating and revoking it makes the old anon and service_role keys stop working, and can sign users out. Do that last, after everything above is on the new keys. See JWT signing keys.
Using Lovable Cloud instead of your own Supabase?
If your app runs on Lovable's built-in backend (Lovable Cloud), you may not have your own Supabase dashboard to change keys in. Ask Lovable support how keys are rotated for your project before changing anything by hand.
A better prompt for Lovable
Paste this into Lovable chat (change the bits in capitals):
My app started failing after I changed my Supabase API keys. The error is: PASTE ERROR.
Please do only this, and show me each change before you make it:
1. Find every place the app reads a Supabase key: src/integrations/supabase/client.ts,
any VITE_ environment variables, and every Edge Function.
2. In the frontend, use only the publishable key (sb_publishable_...). Never put the
secret key or service_role key in frontend code or VITE_ variables.
3. In Edge Functions, read the secret key from SUPABASE_SECRET_KEYS (a JSON object, use
the "default" entry) instead of SUPABASE_SERVICE_ROLE_KEY.
4. Send publishable or secret keys in the apikey header only, never as Authorization: Bearer.
5. Tell me which functions need the built-in JWT check turned off because they are
called with an API key (for example the Stripe webhook).
Do not change database tables, RLS policies or anything else.How to check it's fixed
- Republish, then open the live site in a private window.
- Sign up or sign in, and do the action that was failing.
- In the Network tab, the
.supabase.corequests should return200and theapikeyheader should start withsb_publishable_. - Trigger each webhook or server job once (Stripe has a "send test event" button) and check it no longer returns 401.
- In Settings > API Keys, the legacy keys should show no new use.