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

  1. Log the full beginResponse.data and locate where flow_token actually lives.
  2. Align frontend and backend versions so the begin response contract matches.
  3. If the token sits at a different path after a backend change, update beginResponse.data?.flow_token accordingly.
  4. 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

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


AI-assisted analysis of QuantumNous/new-api@e2c7aa7b10 (2026-08-15). Data as JSON: /api/errors/cb0cf67628702fd8. Report an issue: GitHub.