Ultimate WebAuthn Errors in Production Guide (2026)

WebinarLive on Nov 18: Building a Business Case for PasskeysPasskey Business Case, recording for all registrantsRegister

Back to Overview

Copy PageCopy PageCopied

🇬🇧EnEnglish

Ultimate WebAuthn Errors in Production Guide (2026)

Learn what common WebAuthn errors like NotAllowedError mean in production and how to classify them by operation type, timing and platform context.

Vincent Delitz

Created: February 9, 2026

Updated: October 16, 2026

Table of Contents

Enterprise Passkey Whitepaper

Practical guidance, rollout patterns and KPIs for passkey programs.

Practical guidance, rollout patterns and KPIs for passkey programs.

Get Whitepaper

Share this article


))

Series: Login Failure Debugging

Passkeys Series: Login Failure Debugging

Series overview: all 9 articles

GPS Passkey Error Codes Apple Passkey Error Codes Root Cause Analysis Native App Passkey Errors Passkey Troubleshooting Passkeys in In-App Browsers QR Login Failure WebAuthn Errors Managed Windows Devices

Key Facts

1. Introduction#

In production, WebAuthn errors are confusing because browsers expose a small set of DOMException names (like NotAllowedError) that can represent multiple underlying causes. On top of that, the vast majority of "errors" - often above 95% in optimized large-scale deployments - are actually expected behavior (user aborted the operating system passkey prompt).

Experiment with passkey flows in the Passkeys Debugger.

Try for Free

Important: For privacy reasons, browsers don't distinguish whether the user actively cancelled or whether no passkey existed. However, in some situations and with enough context, both on the web and on native platforms, some of these cases can be differentiated using signals like timing.

If you want the canonical definitions for these names, start with MDN DOMException. For WebAuthn-specific conditions that lead to these exceptions (and what browsers are required to enforce), see the W3C Web Authentication spec.

If you treat all errors as “bugs”, you’ll do the wrong things:

In this article we answer:

Recent Articles

2. A Production Cheat Sheet#

If you only need a quick mapping to unblock debugging, start with this table. It’s biased toward what teams actually see in dashboards and support tickets.

error.name What it usually means in production What to check to confirm First action (UX + engineering)
NotAllowedError User dismissed sheet, timed out or availability mismatch collapsed into one bucket. This is the largest error bucket in production. time-to-error, whether QR/hybrid UI appeared, whether ceremony started from a real user action Treat as expected: restore UI + show fallback
AbortError Your app (or the browser) aborted the ceremony navigation/re-render during ceremony; concurrent WebAuthn calls; AbortController.abort() Enforce one in-flight request; prevent route changes; handle abort as normal control flow
SecurityError Context/policy not allowed origin + RP ID strategy; iframe/embedding; HTTPS; feature policy Fix RP ID/origin configuration; validate embedding policies; ensure secure context
InvalidStateError State mismatch (often duplicate registration) registration vs login; excludeCredentials; existing credential on authenticator Treat as “already enrolled”; adjust UX path; fix option generation
ConstraintError Requirements can’t be satisfied authenticatorAttachment, userVerification, resident key requirements Relax constraints or provide alternative path/fallback. Example: Screen lock is missing on Android
DataError Inputs are malformed/inconsistent base64url encoding; id/challenge/user handle formats Fix encoding/serialization; add validation in option generation
NotSupportedError Platform/browser doesn’t support what you asked OS/browser version; feature detection assumptions Fall back immediately; record segment; avoid showing passkey CTAs for unsupported environments
UnknownError Platform/authenticator failed in a generic way spikes after OS updates; device build; credential-manager provider issues Retry-friendly UX; capture build numbers; investigate segment spikes

One thing that's easy to miss: the same error.name can mean very different things depending on the operation type. Keep the operation context in mind as you read the sections below. In practice, passkey creation (registration) errors typically outnumber login errors by a wide margin - the table above applies to both, but creation is where most of the volume lives.

Next, we'll go deeper on NotAllowedError because it's the one you'll see the most and the one teams misinterpret most often.

3. NotAllowedError: The operation either timed out or was not allowed#

NotAllowedError often looks like "passkeys failed", but it's usually the platform telling you the user did not complete the OS UI. The key is to split it into buckets you can act on.

What you'll see in the browser console:

Source Error message
Chrome, Edge NotAllowedError: The operation either timed out or was not allowed. See: https://www.w3.org/TR/webauthn-2/#sctn-privacy-considerations-client.
Safari, WebKit NotAllowedError: The request is not allowed by the user agent or the platform in the current context, possibly because the user denied permission.
Safari, WebKit NotAllowedError: This request has been cancelled by the user.
Chrome, Edge NotAllowedError: The operation is not allowed at this time because the page does not have focus.
Safari, WebKit NotAllowedError: The document is not focused.
Firefox NotAllowedError: Operation failed.

All of these surface as error.name === "NotAllowedError". The error.message differs by browser engine and underlying cause, but the result is the same: the ceremony did not complete.

This applies to both login and passkey creation. During login (conditional UI, modal with or without allowList), NotAllowedError typically means the user didn't complete the ceremony. During passkey creation, the same error surfaces for different reasons: the user dismissed the creation dialog (conditional create did not work, or the page lost focus during an auto-triggered append. The operation type changes what the error means and what you should do about it.

Timing is often an underrated signal. An error after less than a second of a click is usually an immediate rejection (environment can't do it, document not focused, missing capability). An error after a few seconds is a user cancel (they saw the dialog and decided not to proceed). An error after 30+ seconds is a timeout. On native platforms, timing is especially important: authenticator round-trips, biometric prompts and credential manager handoffs all have characteristic durations that help you separate "didn't work" from "user walked away". You still can't distinguish easily if a passkey existed.

3.1 Disambiguate with Context#

You don't need a perfect signal. You need enough context to avoid treating every NotAllowedError the same way. iOS/Safari gets specific attention below because it has unique constraints (user activation requirements in earlier versions), but in raw error volume, Windows and Chromium browsers often generate more NotAllowedError than any other platform. These signals often get you 80% of the way:

Signal Likely meaning What to do next
Immediate failure (<1s) Environment rejection: no capability, document not focused, conditional create surface unavailable Check feature detection; ensure document has focus; verify operation is supported on this platform
Fast cancel (1-3s) Surprise prompt / no context Change prompt timing; add cooldown after cancel
User-length cancel (3-15s) User saw the dialog and chose not to proceed Expected UX; restore UI + show fallback
Timeout (30s+) Ceremony timed out without user action Bucket as "did not complete"; consider whether the prompt was noticed
QR/hybrid UI appears before failure No local credential available on this device. Note: reliably detecting QR code decisions before they happen requires a passkey intelligence layer that knows whether a usable credential exists on the current device. Gate passkey offers; make "Use phone" explicit; reduce surprise QR
Concentrated on iOS/Safari and triggered without a click/tap Missing user activation Start the ceremony from a real user gesture
During conditional create or auto-triggered append Autofill not available, credential already exists or page lost focus. Conditional create errors can appear suddenly and at high volume when the feature is launched, making this one of the largest error sources overnight. See conditional create; check document visibility state; use getClientCapabilities() to verify conditionalCreate support before attempting the call

This is also why NotAllowedError should rarely be user-visible. It’s not a message the user can act on.

Context classification is also where success rates split most sharply. The Corbado Passkey Benchmark 2026 passkey authentication success rate analysis measures Q1 2026 completion at 55–95% for unknown-device identifier-first flows versus 95–99% for known-device returns across large-scale B2C deployments. iOS web identifier- first lands at 85–95% completion (% CDA 0–5%), Android web at 70–85% (% CDA 5–10%) and macOS web at 70–85% (% CDA 10–15%), while Windows web sits at 45–60% identifier-first completion with % CDA at 55–65% on Windows 11 and 40–55% on Windows 10. Reading NotAllowedError volume without separating known versus unknown device contexts conflates two fundamentally different success regimes.

One nuance that matters in production: some user agents may surface timeouts as TimeoutError, but many teams still see timeouts collapse into NotAllowedError in dashboards. Either way, treat timeouts as “ceremony did not complete” and bucket using timing plus context.

3.2 UX Handling: make Cancel a normal Exit#

When the OS sheet is dismissed or times out, your UI should immediately recover and react gracefully. A practical set of rules:

Beyond the basics:

If your “cancels” are genuinely high, the next step is to fix the root causes behind them: prompt timing, QR surprises and low availability.

3.3 Engineering Fixes that reduce NotAllowedError#

Start with the changes that move metrics quickly:

3.4 Note on evolving Error Names#

Error names are a moving target. There are ongoing proposals to add more granular WebAuthn errors (for example, to separate “no credential available” from “user cancelled”). As of February 2026, this is not implemented in any browsers, so it’s still worth building your own reason buckets based on context and timing. If you want to track this work, see WebAuthn issue #2062 and the "New Error Codes" explainer.

The remaining error names are less frequent but still worth understanding when they appear.

4. AbortError: The operation was aborted#

AbortError is uncommon in volume compared to NotAllowedError, but when it appears it's highly diagnostic: it usually means the ceremony did not finish because your app invalidated the request - navigation happened, state changed or a second request started.

What you'll see in the browser console:

Source Error message
Chrome, Edge AbortError: The operation was aborted.
Chrome, Edge AbortError: Aborted by AbortSignal.
Firefox AbortError: signal is aborted without reason
Firefox AbortError: Operation timed out.
Safari, WebKit AbortError: The user aborted a request.
Chrome AbortError: CredentialContainer request is not allowed.

Common production causes include:

To fix it, focus on making the ceremony a “critical section”:

If you see AbortError concentrated in embedded surfaces or multi-domain apps, the next bucket to check is SecurityError.

5. SecurityError: WebAuthn is not supported on sites with TLS certificate errors#

SecurityError is the browser telling you: "this context is not allowed to do what you asked." In practice it's almost always configuration, not user behavior. In mature production deployments, SecurityError is rare because these issues are typically caught during integration testing. If it appears in production, it usually means a new domain, embedding context or deployment target was added without proper WebAuthn configuration.

Common causes include:

What you'll see in the browser console:

Source Error message
Chrome, Edge SecurityError: WebAuthn is not supported on sites with TLS certificate errors.
Any browser SecurityError: The relying party ID is not a registrable domain suffix of, nor equal to the current origin's effective domain.
Chrome (iframe) SecurityError: The 'publickey-credentials-create' feature is not enabled in this document.

In production, SecurityError is rare - these are almost always caught during integration testing. When they do appear, the TLS certificate error is the most common survivor.

The fastest debugging loop is:

Once SecurityError is handled, the next bucket to treat seriously is the set of errors that often indicate implementation bugs: InvalidStateError, ConstraintError and DataError.

Passkeys Cheatsheet. Practical guidance, rollout patterns and KPIs for passkey programs.

Get Cheat Sheet

6. InvalidStateError, ConstraintError, DataError: treat as Implementation Bugs#

These errors should be rare in a mature passkey implementation. When they show up, they usually indicate that option generation is wrong for a segment or that the flow is in the wrong state.

6.1 InvalidStateError: "credentials already registered with the relying party"#

What you'll see in the browser console:

Source Error message
Safari, WebKit InvalidStateError: The user attempted to register an authenticator that contains one of the credentials already registered with the relying party.
Chrome, Edge InvalidStateError: At least one credential matches an entry of the excludeCredentials list in the platform attached authenticator.
Chrome, Edge InvalidStateError: A request is already pending.
Firefox InvalidStateError: An attempt was made to use an object that is not, or is no longer, usable

Typical meanings:

Practical handling:

6.2 ConstraintError#

Typical meaning: the authenticator can’t satisfy your requested constraints.

Common triggers:

Fix: relax constraints (where acceptable) or provide an alternative path. For missing screen lock, consider detecting this condition and guiding users rather than failing silently (Android).

6.3 DataError#

Typical meaning: inputs are malformed or inconsistent.

Common triggers:

Fix: validate and normalize inputs at the boundary where you generate WebAuthn options. In practice, DataError is effectively absent in mature production systems - if your option generation is tested, you won't see this in dashboards.

If these errors are under control, the next question is coverage: are users failing because the environment can’t do WebAuthn the way you expect?

7. NotSupportedError: The user agent does not support public key credentials#

NotSupportedError is a coverage signal, not a reliability signal. It usually means a segment can't do what you asked (OS/browser too old, missing capability, feature not enabled).

What you'll see in the browser console:

Source Error message
Chrome, Edge NotSupportedError: The user agent does not support public key credentials.
Firefox NotSupportedError: Resident credentials or empty 'allowCredentials' lists are not supported.
Chrome, Edge, Firefox TypeError: PublicKeyCredential.parseCreationOptionsFromJSON is not a function
Chrome, Edge, Firefox TypeError: PublicKeyCredential.parseRequestOptionsFromJSON is not a function
Chrome, Edge, Firefox TypeError: credential.toJSON is not a function
Safari TypeError: Can only call PublicKeyCredential.toJSON on instances of PublicKeyCredential

The first two are genuine NotSupportedError DOMExceptions. The TypeError entries are technically a different exception type but represent the same class of problem: the browser or environment doesn't support what you asked for. The JSON serialization TypeError family is far more common in practice than the NotSupportedError DOMException itself (see below).

Common causes include:

The JSON serialization family is the largest source of NotSupportedError-class failures in production. Technically these surface as TypeError (missing method), not as a DOMException, but this is where you'll encounter them. Two distinct root causes:

  1. Browser doesn't support WebAuthn JSON serialization methods. The browser has navigator.credentials but not PublicKeyCredential.parseCreationOptionsFromJSON / parseRequestOptionsFromJSON. This accounts for roughly 90% of this error family, concentrated in older Safari and Chrome versions. If your client library depends on these methods without a fallback, this produces significant error volume.
  2. Password manager extensions break .toJSON(). Extensions like Bitwarden, LastPass or 1Password can intercept the ceremony and return an object that looks like a credential but isn't a real PublicKeyCredential instance. Calling .toJSON() on it either throws, returns undefined or the object is null entirely. This is roughly 10% of the family but especially confusing to debug because the error messages differ by browser (Safari: "Can only call on instances of PublicKeyCredential"; Firefox: "does not implement interface PublicKeyCredential").

Handling should be blunt and fast:

If coverage looks fine but failures still happen in specific segments, you may be dealing with platform-layer issues surfaced as UnknownError.

8. UnknownError: An unknown error occurred while talking to the credential manager#

UnknownError is a catch-all for authenticator/OS failures that don't map cleanly into the other categories. It's often transient, but it can also spike after OS updates.

What you'll see in the browser console:

Source Error message
Chrome (Android) UnknownError: An unknown error occurred while talking to the credential manager.
Any browser UnknownError: The operation failed for an unknown transient reason.
Any browser UnknownError: Either the device has received unexpected request data, or the device has been reconfigured since the request was made.
Any browser UnknownError: Something went wrong.
Chrome (LastPass) TypeError: Cannot use 'in' operator to search for 'type' in null
Safari (LastPass) TypeError: null is not an Object. (evaluating 'key in input')
Chrome (Bitwarden) FallbackRequested

Practical handling:

One niche source of errors that doesn't fit neatly into any DOMException category: password manager browser extensions (like Bitwarden, LastPass, 1Password and others) can intercept WebAuthn API calls and return non-standard responses. While small in volume compared to user cancellations, these are worth tracking because they affect specific user segments consistently and the symptoms are confusing: missing methods on the returned credential object, unexpected error types or malformed responses that don't match any documented WebAuthn error. These often surface as UnknownError or as unclassified exceptions. If you see error spikes concentrated in specific browsers with no OS-level explanation, check whether a credential manager extension is involved.

So far we've covered web browser error names. But if you also ship native apps, the error landscape is different - and in some ways, significantly better.

9. A Word on native Apps (iOS and Android)#

Everything above covers web browsers. Native apps - iOS with the ASAuthorization framework, Android with Credential Manager - share the same fundamental error categories but differ in important ways:

  1. "No credentials" is a distinct signal. On web, browsers collapse "no credential available" and "user cancelled" into the same NotAllowedError for privacy. On native, using preferImmediatelyAvailableCredentials on iOS (ASAuthorizationController) or setPreferImmediatelyAvailableCredentials(true) on Android (GetCredentialRequest) tells the OS to only present credentials already on the device and fail immediately if none exist. This gives you a clean "no credentials" return that web cannot provide.

  2. Credential provider status is visible. Native platforms in some conditions you can tell you when no credential provider (Google Password Manager, iCloud Keychain, 1Password, etc.) is installed, configured or set as default and react to that. On web, this information is hidden behind opaque NotAllowedError messages.

  3. Error messages are more specific. Because the user has installed the app - and thereby established a trust relationship with the relying party - the OS surfaces more diagnostic detail. The privacy considerations that force web browsers to be vague don't apply in the same way when the app is already on the device. iOS returns localized messages in the user's device language. Android returns structured error types with cause chains. This makes debugging easier but means your error handling must account for localization and platform-specific error formats.

9.1 iOS (ASAuthorization Framework)#

iOS surfaces passkey errors through the ASAuthorization framework. All errors arrive in the authorizationController(controller:didCompleteWithError:) delegate callback as NSError objects.

Classify by domain + code, not by message string. The primary error domain is com.apple.AuthenticationServices.AuthorizationError (ASAuthorizationError.errorDomain). Cast the error with let nsError = error as NSError and match on .domain and .code. Never match on .localizedDescription in production - Apple's messages are localized into 30+ languages and can change across OS versions. The message strings listed below are useful for recognizing errors in logs, but they are not classification criteria.

Public ASAuthorizationError codes:

Code Name Since What it means
1000 Unknown iOS 13 Should not appear in production. Generic catch-all.
1001 Canceled iOS 13 User dismissed the passkey sheet. Most common error overall - the NotAllowedError equivalent. Clean signal with empty userInfo and no underlying error.
1002 InvalidResponse iOS 13 Framework-level corruption. Rare in practice.
1003 NotHandled iOS 13 No provider handled the request. Check entitlements and credential provider configuration.
1004 Failed iOS 13 Generic failure. The localizedDescription contains the actual reason (e.g. "Application with identifier X is not associated with domain Y"). userInfo may contain a FailureReason string, but NSUnderlyingErrorKey is not always populated - domain association failures return nil for the underlying error.
1005 NotInteractive iOS 15 The request ran in a context that cannot show UI (e.g. no interactive foreground scene). Predates passkeys (added in iOS 15, passkeys shipped in iOS 16) and is sometimes misdocumented as the "no credentials" signal for preferImmediatelyAvailableCredentials - that case actually returns an immediate 1001 with a localized no-credentials message.
1006 MatchedExcludedCredential iOS 18 A passkey already exists for this RP on this device. Clean signal for duplicate detection - empty userInfo, no NSUnderlyingErrorKey. Classification works without string matching.
1007 CredentialImport iOS 18.2 Credential import failed.
1008 CredentialExport iOS 18.2 Credential export failed.
1009 PreferSignInWithApple iOS 26 User prefers Sign in with Apple over passkey. New in iOS 26.
1010 DeviceNotConfiguredForPasskeyCreation iOS 26 Device lacks passcode or iCloud Keychain configuration. Known iOS 26 simulator bug: returns 1010 even when configuration is correct (does not reproduce on physical devices).

The most important distinction for production: since iOS 18, duplicate credentials return their own error code 1006 (MatchedExcludedCredential). On iOS 17 and earlier, duplicate credentials were hidden inside code 1004 (Failed). On iOS 18 +, the distinction is structural (different error code), not textual.

Common runtime errors (log-level reference):

These messages appear in localizedDescription or in userInfo for specific failure scenarios. Use them for log searches and debugging, not for programmatic classification.

Message (English locale) Underlying code Notes
Application with identifier <TeamID.BundleID> is not associated with domain X 1004 (Failed) The app's Associated Domains entitlement doesn't match the relying party. Fix the apple-app-site-association file on your server.
Couldn't communicate with a helper application. 1004 (Failed) The credential provider extension failed to respond. Transient - retry is appropriate.
Request already in progress for specified application identifier. 1004 (Failed) A duplicate ASAuthorization request was fired while one was pending. Race condition in the app.
Stolen Device Protection is enabled and biometry is required. 1004 (Failed) iOS 17+ Stolen Device Protection blocks biometric auth in unfamiliar locations. Not actionable by developers, but worth surfacing to the user.
(AuthenticationServicesCore.ASCABLEClient.ClientError error 2.) Separate domain Cross-device authentication (hybrid/CABLE) Bluetooth handshake failed.
(AuthenticationServicesCore.ASCABLEClient.ClientError error 3.) Separate domain Cross-device authentication Bluetooth connection failed.

Localized "no credentials" messages (immediate code 1001):

When preferImmediatelyAvailableCredentials is set and no passkey exists, iOS returns immediately - before any sheet is shown - with code 1001 (Canceled) and a localized message in the user's device language. This is unique to native apps - web browsers never expose this signal. The message always starts with The operation couldn't be completed. followed by the localized text:

Language Message
English No credentials available for login.
Chinese (Simplified) 没有可用于登录的凭证。
Vietnamese Không có sẵn thông tin để đăng nhập.
Arabic لا تتوفر بيانات اعتماد لتسجيل الدخول.
Spanish No hay ninguna credencial disponible para iniciar sesión.
Chinese (Traditional) 沒有可用於登入的憑證。
Korean 로그인을 위한 자격 증명이 없습니다.
French (Canada) Aucun identifiant disponible pour la connexion.
Portuguese (Brazil) Nenhuma credencial disponível para login.
French (France) Aucune information d'identification n'est disponible pour procéder à la connexion.
Thai ไม่มีข้อมูลประจำตัวสำหรับเข้าสู่ระบบ
Italian Non ci sono credenziali disponibili per l'accesso.
Dutch Geen inloggegevens beschikbaar.
Japanese ログイン用の資格情報がありません。
Turkish Oturum açmak için kullanılabilecek kimlik bilgisi yok.

Because the code is the same 1001 as a genuine user dismissal, these strings cannot be your primary classifier either. Classify by request context - the flag was set and the error returned immediately, before any sheet rendered - and use the messages above for recognizing the case in logs.

9.2 Android (Credential Manager API)#

Android surfaces passkey errors through the Credential Manager API (androidx.credentials). Error messages include a primary message and often a cause with additional detail. Compared to iOS, Android provides more structured error types and more explicit causes for configuration issues.

User cancellation and credential detection:

Error Notes
User cancelled the operation User dismissed the passkey prompt. The NotAllowedError equivalent. Note: the Credential Manager also returns User canceled the request (US spelling) from a different code path - both are identical.
Excluded credential matches existing credential A passkey already exists for this credential ID. The InvalidStateError equivalent. Unlike iOS, the message is distinct from user cancellation.
No create options available. No eligible credential provider can handle the creation request. Typically means Google Play Services is outdated or no credential provider supports passkey creation.

Configuration and security errors:

Error Notes
Passkeys not supported for this app Digital Asset Links (assetlinks.json) is missing or does not contain the app's signing certificate fingerprint. The SecurityError equivalent.
Https failed: respCode=301, url=https://<domain>/.well-known/assetlinks.json The assetlinks.json file returns a redirect instead of HTTP 200. Android requires the file at the exact URL without redirects.
The incoming request cannot be validated The Credential Manager cannot verify the request against Digital Asset Links.
RP ID cannot be validated. The relying party ID in the WebAuthn options does not match assetlinks.json.
Screen lock is missing. No PIN, pattern or biometric configured on the device. Passkeys require user verification. The ConstraintError equivalent.
Cannot find an eligible account. No Google account on the device is eligible for passkey creation (rare, typically custom enterprise setups).

Platform and authenticator errors:

Error Notes
Unsuccessful result from folsom activity. Google Play Services internal failure. "Folsom" is a GMS component for passkey operations. Transient - retry is appropriate.
Can't find the proper key to decrypt the private key from WebauthnCredentialSpecifics. A synced passkey exists but the device cannot decrypt its private key. The Google Password Manager sync state is inconsistent - the credential was synced from another device but the decryption key is unavailable. Not actionable by developers.
Operation was interrupted (cause: The UI was interrupted - please try again.) The Credential Manager UI was interrupted by another activity (incoming call, screen rotation, app backgrounded). The AbortError equivalent.
Unknown credential error Generic catch-all when no specific error type applies. Typically transient.
timeout (cause: Canceled) The Credential Manager operation timed out before the user completed biometric verification.

10. Putting it all together: the Error Taxonomy#

The following diagram shows how all the layers discussed above - Infrastructure, Environment, Operation type, Classification and Detection - connect end to end. It is the mental model you should have in mind when designing your error tracking.

Error Analysis & Runtime

The Environment (Client Side)

Infrastructure (Server/Network)

Mitigation Actions

Detection Goals

Layer 5: Modality & Complexity

Layer 4: WebAuthn Operation

Layer 3: Client Software

Layer 2: OS & Version

Layer 1: Platform

Manifests as

Manifests as

Server Side Error

500 / Logic Error

Network Error

Timeout / 400 Bad Request

Client-Side Generic Error

Web / Browser

Native / App

OS: Windows, macOS, Linux

OS: iOS, Android

OS Version / Settings

(Screen Lock, Biometrics)

Browser: Chrome, Safari, Edge

+ Version

Native App / WebView

+ App Version

Operation Type

Login

(navigator.credentials.get)

Registration

(navigator.credentials.create)

CUI / Conditional UI

Automatic Operation

CDA / Cross-Device Auth

Classification

Expected Error

(User Aborted Ceremony)

Unexpected Error

(System/Crash/Unknown)

P1: Login Problems

P2: Creation Problems

Detect Baseline Drift

(Increased Expected)

Detect New Anomalies

(Increased Unexpected)

Deploy Hotfix / New Version

Activate Automatic Fallback

(Disable Passkey)

Click to enlarge and copy chart definition

The key insight: a raw error.name only makes sense once you know which layer of the Environment produced it, which Operation was running and whether the error was Expected or Unexpected. The sections below cover what to log and how to act on it.

11. What to log so Errors become debuggable#

Most of the error classification in this article can be done with client-side signals alone. A frontend-only observability SDK captures enough context to classify the vast majority of WebAuthn errors. This is also how Corbado Observe is architected: the client-side layer handles error attribution, timing, operation context and platform detection. Server-side logging adds a second layer for failures that only the backend can see.

The key requirement: every attempt must be joinable end-to-end. A shared correlation id (e.g. auth_flow_id) connects client-side context with the server verification outcome.

11.1 Client-Side Signals (Frontend SDK)#

Signal Why it matters
error.name + normalized reason bucket Raw browser error + your classification
Operation type Conditional UI, modal login, manual create, conditional create, auto-append. CDA (Cross-Device Auth) is its own complex beast.
Full Environment Context OS + Version, Browser + Version, Hardware Brand/Model, Authenticator Settings (e.g., GPM enabled?)
Authenticator / credential manager context Extension and provider breakage
Time-to-error from operation start Immediate rejection (<1s) vs user cancel (1-15s) vs timeout (30s+)
Network / Connectivity Status Network errors often manifest as client errors. Track if the user was offline and queue logs to send when connectivity restores.
Whether QR/hybrid UI appeared Local vs cross-device failure
Correlation id (auth_flow_id) Join with server logs

11.2 Server-Side Signals (Backend Verification)#

Server verification failures happen after the browser returns a credential and signed challenge. These should be structured errors with explicit codes, not mixed into the same bucket as client-side DOMException names. See: WebAuthn server implementation.

Signal Why it matters
Challenge mismatch / expired challenge Session timing or replay issues
Origin / RP ID mismatch Multi-domain configuration bugs
Invalid signature / credential not found Deleted or corrupted credential. Common case: conditional UI login with a passkey the user already deleted server-side. Use the Signal API to keep client and server credential lists in sync.
User handle mismatch Account mapping issues
Correlation id (auth_flow_id) Join with client-side context

If you want a full funnel model (drop-offs by step and conversion between steps), see: passkey telemetry to understand drop-offs.

Subscribe to our Passkeys Substack for the latest news.

Subscribe

Passkey Funnel Analysis: Process Mining for Authentication | Corbado Connect - YouTube

Tap to unmute

Passkey Funnel Analysis: Process Mining for Authentication | Corbado Connect Corbado

Corbado181 subscribers

With this data in place, the conclusion becomes simple: most “errors” become either UX fixes, coverage fixes or configuration fixes. But building and maintaining this classification yourself is significant ongoing work.

12. How Corbado can help#

Everything above comes down to one problem: error.name alone cannot tell you whether a failure was a user cancel, a timeout, an environment rejection or a real regression. Building that classification in-house means chasing browser and OS message changes forever. Corbado Observe is the authentication observability layer that does this for you. It is a frontend integration that sits on top of your existing passkey stack, works with any WebAuthn server and any IDP and classifies every ceremony automatically.

Look's like your browser doesn't support this video.See Passkey Errors in Corbado Observe → 12.1 Why error.name alone is not enough#

The same NotAllowedError can mean several different things depending on three dimensions browsers never separate for you. Observe reconstructs all three from every ceremony.

Dimension What browsers give you What you actually need Example
Operation context NotAllowedError Whether it was conditional UI, modal login, manual create, conditional create or an auto-append Android returns the same "unknown error" for a login dismiss (expected) and a creation failure (unexpected)
Timing No duration data Immediate (<1s) vs user cancel (1-15s) vs timeout (30s+) 200ms means environment rejection, 5s means the user saw the dialog and cancelled, 35s means timeout
Platform + authenticator Generic error.name OS, browser, version and credential manager for every error "user dismissed dialog" on Chrome and "autofill unavailable" on Safari both surface as NotAllowedError

12.2 What Corbado Observe captures#

Observe is a lightweight frontend integration that sits on top of your existing passkey implementation and classifies every WebAuthn error along all three dimensions. Each capability maps to a view in the Observe console.

Capability Observe view What it does
Error attribution Passkey Errors Records OS, OS version, browser, browser version and authenticator with every ceremony, so a spike resolves to a concrete segment instead of a raw NotAllowedError count
Operation and timing Subflow Explorer Ties each error to its operation and to the time-to-error from ceremony start, with p50 and p90 timing per step so immediate rejections, user cancels and timeouts separate cleanly
Error classification Passkey Errors Matches on full error context beyond the raw name, ranks patterns by OS, browser and login method and splits expected user aborts from unexpected system failures
Capability coverage Client Capabilities, Technology Shows which environments can run conditional UI, conditional create and platform authenticators, so NotSupportedError-class gaps map to concrete segments
Anomaly detection Markets, Touchpoints Flags baseline drift (a slow rise in expected cancels after an OS update) and sudden spikes (a new browser version or IDP change) and cross-correlates them with the KPI they moved

The result is visibility into what is happening, without changing your implementation.

12.3 Two ways to investigate: top-down and bottom-up#

The Observe console supports two investigation paths that converge on the same root cause.

Top-down (dashboard to root cause):

  1. Watch for two anomaly types. Baseline drift is a gradual rise in user cancels for a specific environment (for example iOS 18.2 on iPhone 15) after an OS update. Spikes are a new error or a sudden jump in failures that points to a breaking change or a regression in a new browser version.

  2. Prioritize by impact. Treat a drop in login success as urgent and keep creation-side problems on trend watch, so a user-cancel spike during enrollment does not outrank a broken sign-in.

  3. Drill into the environment. Use the granular dimensions (hardware, authenticator settings) to tell whether the issue is global or specific to Samsung devices on Android 14.

Bottom-up (error patterns to impact):

  1. Start from the Passkey Errors view and review the classified patterns and their volumes.
  2. Refine error mappings as new patterns appear, for example a new browser version shipping a different message for the same condition.
  3. Read the KPI annotations: patterns are cross-correlated with metric changes and surfaced on dashboards, so a spike in one pattern links to the metric it moved.

Both paths converge: top-down tells you something is wrong, bottom-up tells you why.

You keep your current passkey implementation and get the classified, segmented error view this article argues for, without building and maintaining the pattern library yourself.

Get free passkey whitepaper for enterprises.

Get for free

13. Conclusion#

WebAuthn error names are not a verdict. They're hints - and they only become actionable when you connect them to the operation type, the timing and the platform context.

Two rules of thumb that cut across all error types: never show raw browser errors to users - always provide a clear fallback path - and separate local attempts from QR/hybrid cross-device attempts, because they fail for different reasons and need different fixes. At scale, maintaining error classification across browsers, OS versions and credential managers is ongoing work. Consider using an observability SDK with a maintained pattern library rather than building this from scratch.

About Corbado

Corbado is the Passkey Intelligence Platform for large-scale CIAM teams running consumer authentication. We help you see what IDP logs and generic analytics tools can't: where passkeys, passwords, OTP, social login and fallback journeys succeed, stall or fail, which devices and browsers create friction, and when an OS update silently breaks login. Two products: Corbado Observe layers process mining and observability across authentication journeys. Corbado Connect adds managed passkeys with analytics built in alongside your IDP. VicRoads runs passkeys for 5M+ users with Corbado (more than 80% passkey activation rate). Talk to a Passkey Expert →

Frequently Asked Questions# How do I tell the difference between a user cancelling a passkey prompt and the device not having a passkey?#

On web, browsers collapse both cases into NotAllowedError for privacy reasons, so you cannot distinguish them directly. Use timing as a proxy: an error under 1 second typically means environment rejection or missing capability, while 1-15 seconds suggests the user saw and dismissed the dialog. On native iOS and Android apps, setting preferImmediatelyAvailableCredentials before the request gives you a clean "no credentials" signal before any UI is shown (on iOS an immediate code 1001 return with a localized no-credentials message, on Android via GetCredentialRequest).

Why is my WebAuthn error rate so high even though passkeys seem to be working for most users?#

In optimized large-scale passkey deployments, over 95% of recorded WebAuthn errors are expected user aborts, not system failures. Tracking raw error.name counts without separating "user dismissed prompt" from genuine failures inflates error metrics and masks real regressions hidden inside the NotAllowedError volume. Split your counts by operation type and treat user-cancel buckets separately from unexpected errors like SecurityError, ConstraintError and DataError.

How do I detect "no passkey on this device" on iOS with preferImmediatelyAvailableCredentials?#

When preferImmediatelyAvailableCredentials is set on ASAuthorizationController and no passkey exists on the device, iOS returns immediately - before any sheet is shown - with code 1001 (Canceled) and a localized no-credentials message (English: "No credentials available for login."). There is no dedicated error code: a genuine user dismissal returns the same 1001, so classify by the flag being set plus the immediate return before any UI rendered, and treat the message string only as confirmation because Apple localizes it into 30+ languages. Code 1005 (NotInteractive) is a different signal - the request ran in a context that cannot show UI.

How should I handle NotAllowedError during a conditional create or auto-triggered passkey append flow?#

During conditional create, NotAllowedError, AbortError and InvalidStateError are all expected outcomes and should be silently ignored rather than surfaced as errors. NotAllowedError indicates autofill is unavailable or the page lost focus, while InvalidStateError means a passkey already exists in the provider. Before attempting the call, use getClientCapabilities() to verify conditionalCreate support and check document visibility state to avoid inflating your error counts.

What context should I log alongside error.name to make WebAuthn failures actually debuggable?#

Capture operation type (conditional UI, modal login, manual create, conditional create or auto-append), time-to-error from operation start, OS version, browser version, hardware model, whether QR/hybrid UI appeared and a correlation ID to join with server-side logs. Server verification failures such as challenge mismatch, invalid signature and credential not found should be logged as explicit structured codes, not mixed into the same bucket as client-side DOMException names. These signals together allow classification of the vast majority of errors into actionable buckets without guessing.

See how Corbado fits your passkey rollout and existing authentication stack.

Explore the Console

Share this article


))

Related Articles

\ \ WebAuthn Immediate Mediation explained \ \ Vincent Delitz - June 20, 2025](/content/blog/webauthn-immediate-mediation/index.html)

\ \ 9 WebAuthn Server Implementation Libraries Compared \ \ Nicolai - December 15, 2023](/content/blog/webauthn-server-implementation/index.html)

\ \ WebAuthn Server Options: Overview of Early Adopters \ \ Vincent Delitz - July 9, 2024](/content/blog/webauthn-server-options-overview/index.html)

\ \ WebAuthn Passkey QR Codes & Bluetooth: Hybrid Transport \ \ Vincent Delitz - November 8, 2023](/content/blog/webauthn-passkey-qr-code/index.html)

\ \ Passkeys & iframes: How to Create & Login with a Passkey? \ \ Vincent Delitz - June 3, 2024](/content/blog/iframe-passkeys-webauthn/index.html)

\ \ Safari WebAuthn User Gesture Requirements Not Detected \ \ Vincent Delitz - May 14, 2024](/content/blog/safari-webauthn-user-activated-events/index.html)

Previous slideNext slide

Table of Contents