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

# WebNfcError

> A Web NFC scan failed in a way the interface should present distinctly. Carries a typed `reason` so you can branch instead of guessing.

```ts theme={null}
class WebNfcError extends Error {
  readonly name: "WebNfcError";
  readonly reason: WebNfcFailureReason;
}

type WebNfcFailureReason =
  | "permission-denied"
  | "permission-not-requested"
  | "empty-tag"
  | "unreadable-tag"
  | "not-supported"
  | "nfc-disabled";
```

The rejection [`webNfcTapSource`](/sdks/web/reference/tap-sources/webNfcTapSource) throws when a scan fails in a way worth surfacing. Every failure is typed so the interface can react by `reason` - re-prompt, point at a settings screen, or fall back to opening the tap link - rather than reading `message`. A readable tag that is not an Endstate chip is not an error: the source resolves `null` for that.

## Example

```ts theme={null}
import { WebNfcError } from "@endstate-sdk/web";

try {
  const tap = await client.captureTap();
  if (tap) await client.verify(tap);
} catch (error) {
  if (error instanceof WebNfcError) {
    switch (error.reason) {
      case "permission-denied":
        // Send the customer to the browser's site settings.
        break;
      case "permission-not-requested":
        // Retry from a control that is not under an overlay.
        break;
      default:
        // empty-tag, unreadable-tag, not-supported, nfc-disabled:
        // show "tap the product" and open the tap link instead.
        break;
    }
  }
}
```

## Properties

<ResponseField name="name" type="string" required>
  Always `"WebNfcError"`.
</ResponseField>

<ResponseField name="reason" type="WebNfcFailureReason" required>
  What failed. Branch on this, never on `message`.
</ResponseField>

<ResponseField name="message" type="string" required>
  A human-readable description of the failure. Diagnostic only; do not branch on
  it.
</ResponseField>

<ResponseField name="cause" type="unknown">
  The underlying browser error, when one triggered the failure. Present for the
  DOM-error reasons; absent for `empty-tag`, which the source raises itself.
</ResponseField>

## Reasons

Each reason maps to a distinct thing to present. The last four all mean the same fallback - open the tap link - but stay separate so you can explain why.

| Reason                     | What happened                                                                                                    | What to present                           |
| -------------------------- | ---------------------------------------------------------------------------------------------------------------- | ----------------------------------------- |
| `permission-denied`        | Scanning is blocked for this site                                                                                | The browser's site settings for this page |
| `permission-not-requested` | The prompt was dismissed, or was suppressed and never appeared (commonly an overlay over the triggering control) | Retry from an unobstructed control        |
| `empty-tag`                | A tag was detected but returned no content, a known failure mode of some device NFC stacks                       | Open the tap link instead                 |
| `unreadable-tag`           | A tag was detected but could not be read                                                                         | Open the tap link instead                 |
| `not-supported`            | The device has no usable reader                                                                                  | Open the tap link instead                 |
| `nfc-disabled`             | Reading is turned off in the device settings                                                                     | Turn it on there, or open the tap link    |

<Note>
  `permission-denied` and `permission-not-requested` are distinguished by
  re-querying the origin's permission after the browser reports a
  `NotAllowedError`: a still-askable `"prompt"` state becomes
  `permission-not-requested`, otherwise `permission-denied`. Use
  [`webNfcPermissionState`](/sdks/web/reference/tap-sources/webNfcPermissionState)
  to gate the interface before a scan so you can avoid the denied path
  altogether.
</Note>

## See also

* [`webNfcTapSource`](/sdks/web/reference/tap-sources/webNfcTapSource) - the source that throws this.
* [`webNfcPermissionState`](/sdks/web/reference/tap-sources/webNfcPermissionState) - check the permission before offering a scan.
