Short answer
Supabase says the most common cause is email prefetching: a mail client or security tool, such as Safe Links in Microsoft Defender for Office 365, opens the link to check it and uses up the one-time token. Stop the link from consuming the token: put the 6-digit code {{ .Token }} in the email template, or link to a page of your own where the user enters it.
Why the link fails, and the fix
01
An email scanner opened the link first
“The most common reason for OTP tokens appearing expired or invalid before a user can even use them is email prefetching.” Supabase, OTP verification failures
Security tools, often ones companies use, open links in incoming email to check them. That visit uses up the token, so when the user clicks, it has already gone.
02
Send a code instead of a link
“Use an email OTP instead by including {{ .Token }} in the email template” Supabase, Email templates
Change the confirmation or reset template to show the 6-digit code, and add a screen where the user types it, which you check with supabase.auth.verifyOtp(). A scanner that opens links then has nothing to use up.
03
Link to your own page, not to the token
“Create your own custom email link to redirect the user to a page where they can enter their email and token to sign in” Supabase, Email templates
To keep a button in the email, point it at a page on your site and let the user confirm from there, so opening the link doesn’t consume the token.
04
Confirm it in the logs
“If you see rapid 403 errors or otp_expired messages shortly after an email is sent, but before a user could reasonably click the link, it strongly suggests prefetching.” Supabase, OTP verification failures
Look for 403 responses on the /verify endpoint in the API logs and otp_expired in the Auth logs, and compare their times with when the email went out.
Checked against the vendor’s docs on : Supabase troubleshooting: OTP verification failures (otp_expired); Supabase docs: Email templates.
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 Email not confirmed · Supabase email rate limit exceeded · Supabase auth emails going to spam · Sign in with Apple invalid_client