QuantumNous/new-api · error · Error
Verification flow expired
Error message
Verification flow expired
What it means
Thrown in verifyPasskey() when the begin endpoint succeeds but the response data contains no flow_token. The passkey flow is two-phase (begin → finish) and the flow token links them; without it the finish call cannot be made. The code treats a missing flow token as an expired/invalid verification flow.
Source
Thrown at web/src/features/auth/secure-verification/api.ts:149
throw new Error(
i18next.t('Passkey verification is not supported in this environment')
)
}
try {
const beginResponse = await beginPasskeyVerification(scope)
if (!beginResponse.success) {
throw new Error(
beginResponse.message || i18next.t('Failed to start verification')
)
}
const publicKey = prepareCredentialRequestOptions(
beginResponse.data?.options ?? beginResponse.data
)
const flowToken = beginResponse.data?.flow_token
if (!flowToken) {
throw new Error(i18next.t('Verification flow expired'))
}
const credential = (await navigator.credentials.get({
publicKey,
})) as PublicKeyCredential | null
if (!credential) {
throw new Error(i18next.t('Passkey verification was cancelled'))
}
const assertion = buildAssertionResult(credential)
if (!assertion) {
throw new Error(i18next.t('Unable to build Passkey assertion'))
}
const finishResponse = await finishPasskeyVerification(flowToken, assertion)
if (!finishResponse.success) {
throw new Error(View on GitHub (pinned to e2c7aa7b10)
Solutions
- Log the full beginResponse.data and locate where flow_token actually lives.
- Align frontend and backend versions so the begin response contract matches.
- If the token sits at a different path after a backend change, update beginResponse.data?.flow_token accordingly.
- Retry the flow — transient in-memory flow-store eviction resolves on a fresh begin.
Defensive patterns
Strategy: retry
Type guard
const hasFlowToken = (
d: unknown
): d is { flow_token: string } =>
typeof d === 'object' &&
d !== null &&
typeof (d as { flow_token?: unknown }).flow_token === 'string' Try / catch
try {
return await verify('passkey', scope)
} catch (e) {
if (/flow expired/i.test(getErrorMessage(e))) {
return await verify('passkey', scope) // one fresh begin/finish cycle
}
throw e
} Prevention
- Pin frontend/backend versions during deploys — flow_token placement is a contract
- Retry once on missing flow_token; begin creates a fresh flow
- Log beginResponse.data shape when the token is absent to catch drift early
When it happens
Trigger: Begin endpoint returns success:true with data lacking flow_token (version skew or response-shape change); flow store on the backend evicted the token before the response was serialized; a proxy or interceptor rewriting the response body.
Common situations: Frontend/backend version mismatch during deploys; backend reads flow_token from data.options.flow_token but frontend expects data.flow_token after a shape refactor.
Related errors
- Passkey verification is not supported in this environment
- Failed to start verification
- Passkey verification was cancelled
- Unable to build Passkey assertion
- Passkey verification failed
AI-assisted analysis of QuantumNous/new-api@e2c7aa7b10 (2026-08-15).
Data as JSON: /api/errors/cb0cf67628702fd8.
Report an issue: GitHub.