> ## Documentation Index
> Fetch the complete documentation index at: https://docs.corbado.com/llms.txt
> Use this file to discover all available pages before exploring further.

# WebAuthn Signal API

> The Signal API lets a website ask credential managers to align stored passkeys with the credentials its server accepts. Without it, deleted or unknown passkeys can keep appearing in the account chooser menu and fail after local device authentication.

## Keeping the Authenticator and your Server in Sync

Your server and the user's credential manager hold separate passkey records and they are not synchronized automatically. If they differ, the account chooser may offer a passkey that the server no longer accepts. The user can complete local device authentication before the login fails. The stale entry may also remain available on later attempts until the credential manager updates it.

Where the platform and credential provider support it, signalling can reduce that gap. It is a progressive, best-effort lifecycle capability rather than a UI flow or authoritative source of truth; unsupported integrations must continue to work without it.

On the web the stale credential surfaces through [Conditional UI](/passkey-ui-flows/web/passkey-login/conditional-ui), which offers whatever the credential manager holds regardless of what your server still accepts.

<Info>
  **Support (August 2026):** Chrome and Edge 132+ on desktop, Chrome 144+ on Android, Safari 26 (iOS 26, macOS 26). Firefox has no implementation. Current Android WebView can expose Signal methods when its WebAuthn/Credential Manager integration and app-to-site association are configured; [Chromium added WebView Signal request support in March 2026](https://chromium.googlesource.com/chromium/src/+/a51af719c56a438246e49cf4b953b443c017a7b1), so use method-level feature detection rather than a blanket WebView exclusion. For native apps see the [native Signal API](/passkey-ui-flows/native/signal-api).
</Info>

### The three signals

| Method                         | Addresses                                                                                                    | Call it                                                          |
| ------------------------------ | ------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------- |
| `signalUnknownCredential`      | A passkey your server has no record of stays in the account chooser and fails on every attempt               | When a login fails because the credential is unknown             |
| `signalAllAcceptedCredentials` | A passkey the user deleted in your settings keeps being offered, making your own settings screen look broken | After every successful login, and after a user deletes a passkey |
| `signalCurrentUserDetails`     | A stale username or display name in the account chooser, so users pick the wrong account                     | After the user changes their email, username or display name     |

<Steps>
  <Step title="On a failed login: signal the unknown credential">
    * When an assertion arrives for a credential your server does not recognize, call `signalUnknownCredential({ rpId, credentialId })`.
    * This carries no user identifier and no credential list, so it is safe to call when the user is **not** authenticated.
    * Google Password Manager hides rather than deletes the passkey, so it can be restored if the signal was sent in error.
  </Step>

  <Step title="On a successful login: reconcile the full list">
    * Call `signalAllAcceptedCredentials({ rpId, userId, allAcceptedCredentialIds })` with every credential ID your server currently accepts for that user.
    * The provider may hide or remove passkeys missing from the list. If it supports restoration, credentials included again in a later complete list may be unhidden.
  </Step>

  <Step title="After the user deletes a passkey in settings">
    * Call `signalAllAcceptedCredentials` again from your [passkey management](/passkey-ui-flows/web/passkey-management) screen so the provider can hide or remove the credential immediately, rather than waiting until the user's next login to receive the updated list.
  </Step>

  <Step title="After a profile change">
    * Call `signalCurrentUserDetails({ rpId, userId, name, displayName })` so the account chooser shows current information.
    * Note that if the user has manually edited the name in Google Password Manager, their edit wins and your signal will not override it.
  </Step>
</Steps>

<Warning>
  **Use the right signal for an unknown credential.** Call `signalUnknownCredential` only when the server conclusively rejects the assertion because its credential ID is unknown. Do not signal cancellations, invalid signatures, expired challenges, network failures or other login errors. Never call `signalAllAcceptedCredentials` for an unauthenticated caller: it requires a user ID and a complete server-side list.
</Warning>

<Warning>
  **Never send a partial or accidentally empty credential list.** The reconcile signal may hide or remove every passkey it does not find in the accepted list. An empty list is valid only when the authoritative server state confirms that the authenticated user has no accepted credentials, for example after deleting their final passkey. Treat the code that assembles this list as high-risk, and test it against accounts with zero, one and several credentials.
</Warning>

### Why server-side Revocation still needs Client Cleanup

Revoking the credential in authoritative server state makes it unusable immediately: every later assertion from it must be rejected. The server may retain an inactive record for audit. What revocation cannot do is remove the stale entry from the user's locally installed credential manager, because **your backend has no direct channel to the user's password manager**.

Client-side cleanup can happen only when the user's client runs again. Signal an unknown credential only after the server confirms that the submitted credential is unknown. Reconcile the complete accepted list on a documented authenticated schedule, including after authoritative credential-set changes and selected refresh points. The schedule should account for capability, coalescing and rate limits; revocation itself must never wait for signalling.

<Tip>
  Signals resolve with no information about what the provider did. You never learn whether a credential existed or whether the provider acted, which is deliberate and privacy-preserving. It also means signalling can never serve as an audit trail or a source of truth. Treat it as fire-and-forget hygiene, and never block a UI flow on the result.
</Tip>

On the web the reconcile call is `signalAllAcceptedCredentials`, and the list it hides against is `allAcceptedCredentialIds`.

### Error handling

Signal methods can reject with:

* `SecurityError` (`DOMException`): the relying party domain is not valid
* `TypeError`: a `credentialId`, `userId` or list entry is not valid base64url

Branch on `error.name` only. Message strings are not part of the contract.

## Acceptance criteria

| Benchmark as          | User reach |
| --------------------- | ---------- |
| Supporting capability | Low        |

Low user reach does not mean low reliability importance. Signalling does not itself create or promote passkeys, but it reduces stale credential choices and repeated post-verification failures across login and management flows.

The Signal API is intentionally privacy-preserving and opportunistic. Acceptance can verify the relying party's trigger, payload and error handling, but must never require confirmation that a credential provider updated, hid or removed a passkey.

| ID          | Level   | Acceptance criterion                                                                                                                                                           | How to verify                                                                                                                                                       |
| ----------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `W3.2-AC01` | Core    | Each signal method is capability-detected and used as progressive enhancement; lack of support does not break login, profile updates or passkey management.                    | Run every trigger path with the relevant method present and absent; the main operation succeeds in both cases.                                                      |
| `W3.2-AC02` | Core    | `signalUnknownCredential` is invoked only after the server conclusively rejects an assertion because its credential ID is unknown.                                             | Test an unknown credential, invalid signature, expired challenge, network error and user cancellation; only the confirmed unknown-credential case emits the signal. |
| `W3.2-AC03` | Core    | Confirmed server-side deletion or revocation triggers authenticated full-list reconciliation on a supporting client.                                                           | Revoke a credential in an authenticated session and inspect the client call; one full-list reconciliation is invoked after the server update.                       |
| `W3.2-AC04` | Core    | The full-list assembly path prevents partial or accidentally empty snapshots; an empty list is sent only when server state confirms that the user has no accepted credentials. | Simulate partial data, pagination failure, timeout and a legitimate zero-credential account; only the confirmed zero-credential state emits an empty list.          |
| `W3.2-AC05` | Core    | Completion, rejection or absence of a signal never gates successful authentication, server revocation or profile updates.                                                      | Resolve, reject, omit and delay each signal; the main operation reaches the same final state in every case.                                                         |
| `W3.2-AC06` | Core    | A documented authenticated refresh schedule invokes full-list reconciliation at its selected convergence points.                                                               | Run the selected trigger, such as successful sign-in, across two due intervals and one interval where it is not due; calls follow the documented schedule.          |
| `W3.2-AC07` | Core    | Every signal carries the expected RP ID, and every user handle or credential ID field is valid Base64URL for the binary value stored by the server.                            | Capture each signal, decode every encoded field and compare it with the server record; use a wrong RP ID and malformed encoding as negative controls.               |
| `W3.2-AC08` | Core    | A resolved signal confirms only that its options were well formed. It does not prove that a provider updated, hid or removed a credential.                                     | Resolve each method in a test fixture without changing provider state. No security record or user-facing message should claim that the provider acted.              |
| `W3.2-AC09` | Core    | An unauthenticated unknown-credential signal contains only the attempted credential ID and RP ID, without a user handle or accepted-credential list.                           | Capture the signal after a confirmed unknown-credential rejection; its payload contains exactly the method's required fields.                                       |
| `W3.2-AC10` | Core    | Every full-list reconciliation carries the correct opaque WebAuthn user handle and complete server credential set for that account.                                            | Compare captured payloads with server records for zero, one and several credentials; the decoded handle and credential set match exactly.                           |
| `W3.2-AC11` | Quality | A confirmed username or display-name change triggers `signalCurrentUserDetails` with the current account details on a supporting client.                                       | Change each field independently and together; the resulting signal contains the current server values for that account.                                             |
| `W3.2-AC12` | Quality | Updating passkey display metadata through signalling does not create or replace a server credential record.                                                                    | Change account display details and compare credential records before and after the signal; credential IDs and binding records are unchanged.                        |

### References

* **Relevant criteria:** **W3.2-AC01–AC12**: [Web Authentication Level 3: Signal Credential Changes to the Authenticator](https://www.w3.org/TR/webauthn-3/#sctn-signal-methods) defines method inputs, privacy semantics, complete-list safety, opportunistic behavior and the meaning of a resolved promise.
* **Relevant criteria:** **W3.2-AC01–AC09 and W3.2-AC11–AC12**: [Chrome for Developers: Keep passkeys consistent with credentials on your server](https://developer.chrome.com/docs/identity/webauthn-signal-api) supports capability checks, lifecycle triggers, payload construction, metadata updates and warnings against incomplete lists.
* **Relevant criteria:** **W3.2-AC02–AC12**: [Corbado: WebAuthn Signal API](https://www.corbado.com/blog/webauthn-signal-api) supports stale-credential and metadata use cases, authenticator availability, non-blocking integration and privacy-preserving provider uncertainty.

### Further reading

* [Signal API](https://developer.chrome.com/docs/identity/webauthn-signal-api) and [Signal API on Android](https://developer.chrome.com/blog/signal-api-android), Chrome for Developers
* [Web Authentication Level 3, signal methods](https://www.w3.org/TR/webauthn-3/#sctn-signal-methods), W3C
* [WebAuthn Signal API explained](https://www.corbado.com/blog/webauthn-signal-api), Corbado blog
