Short answer
Treat the code as single use. On your handler page, read oobCode from the URL, check it once with verifyPasswordResetCode or checkActionCode, and only then apply it. If the check fails, show a button that sends a new email instead of a raw error.
Why Firebase rejects the code, and the fix
01
The link was already used
“Thrown if the action code is invalid. This can happen if the code is malformed or has already been used.” Firebase, firebase.auth.Auth (applyActionCode)
The person clicked the link twice, or an earlier tab already applied it. Your own page can also apply it twice: an effect that runs twice in development, or a handler that calls applyActionCode on every render. Some email security scanners open links before the person does, so a handler that applies the code on page load can use it up; let the person press a button first. Apply the code once, then send the person on.
02
The link expired
“Thrown if the action code has expired.” Firebase, firebase.auth.Auth (applyActionCode)
Reset and verification links do not last forever. An old email, or a link opened days later, fails with auth/expired-action-code. Offer to send a new link from the error screen.
03
A custom handler reads the wrong value
“A one-time code, used to identify and verify a request” Firebase, Create custom email action handlers
With a custom email action handler, the code arrives in the oobCode query parameter, next to mode and continueUrl. A handler that reads a different name, loses the query string in a redirect, or trims the code passes Firebase something it never issued.
const params = new URLSearchParams(window.location.search); const code = params.get('oobCode'); if (!code) return; // no code in the link try { const email = await verifyPasswordResetCode(auth, code); // show the new-password form for email } catch (e) { // invalid or expired: offer to send a new link }04
The handler uses a different Firebase project
A code from the production project fails on a page set up with the staging config, and the other way round. Check the handler page initialises the same project that sent the email.
05
Reading the message in your app
“The action code is invalid. This can happen if the code is malformed, expired, or has already been used.” GitHub, firebase-js-sdk auth error messages
The web SDK’s message lists all three reasons. Show the person a clear next step instead.
Checked against the vendor’s docs on : Firebase reference: firebase.auth.Auth error codes; Firebase docs: Create custom email action handlers; firebase-js-sdk: auth error messages.
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. This applies to the first 10 Launch Checks bought after October 30, 2026.See scope and start
Related: Firebase auth/weak-password · Firebase auth/unauthorized-domain · Supabase email link is invalid or has expired · Firebase auth/invalid-email