> ## 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.

# useTap

> Trigger an in-page Web NFC scan from a control the customer presses, and read the origin's scan permission to decide whether to offer it.

```ts theme={null}
function useTap(): {
  scan: () => Promise<void>;
  status: "idle" | "scanning" | "error";
  error: Error | null;
  permission: WebNfcPermissionState;
};

type WebNfcPermissionState = "granted" | "prompt" | "denied" | "unsupported";
```

`useTap` drives an in-page scan for customers already on your page. `scan()` runs the Web NFC source and verifies whatever it reads, adopting the result as the provider's active session - so [`useSession`](/sdks/web/reference/react/useSession) updates in place. It is a progressive enhancement over the landing redirect [`EndstateProvider`](/sdks/web/reference/react/EndstateProvider) already handles, not a replacement.

Web NFC needs a user gesture, so call `scan()` from a control the customer presses - never at mount - so the browser's permission prompt can appear. `permission` reports the origin's Web NFC state so you can decide whether to offer the scan at all.

## Example

```tsx theme={null}
"use client";
import { useTap } from "@endstate-sdk/web/react";

export function ScanButton() {
  const { scan, status, permission } = useTap();

  if (permission === "unsupported") return null; // rely on the landing redirect
  if (permission === "denied") {
    return <p>Enable NFC scanning in your browser's site settings.</p>;
  }

  return (
    <button onClick={() => scan()} disabled={status === "scanning"}>
      {status === "scanning" ? "Scanning..." : "Scan another item"}
    </button>
  );
}
```

## Returns

<ResponseField name="scan" type="() => Promise<void>">
  Runs an in-page Web NFC scan and verifies the tap, updating the active
  session. Call it inside a user gesture. It captures only the Web NFC source,
  so it never re-reads the landing redirect.
</ResponseField>

<ResponseField name="status" type="&#x22;idle&#x22; | &#x22;scanning&#x22; | &#x22;error&#x22;">
  `"idle"` at rest, `"scanning"` while a scan runs, `"error"` when it fails.
  Verification progress itself surfaces through
  [`useSession().status`](/sdks/web/reference/react/useSession).
</ResponseField>

<ResponseField name="error" type="Error | null">
  The failure from the last scan, or `null`. A scan can reject even from
  `"granted"` or `"prompt"` - a dismissed prompt, an empty-tag read - as a typed
  [`WebNfcError`](/sdks/web/reference/tap-sources/WebNfcError); branch on its
  `reason`.
</ResponseField>

<ResponseField name="permission" type="WebNfcPermissionState">
  The origin's Web NFC permission: `"granted"`, `"prompt"`, `"denied"`, or
  `"unsupported"`. Offer the scan on `"granted"` or `"prompt"`, route the
  customer to site settings on `"denied"`, and fall back to the redirect on
  `"unsupported"`. See
  [`webNfcPermissionState`](/sdks/web/reference/tap-sources/webNfcPermissionState).
</ResponseField>

## See also

<CardGroup cols={2}>
  <Card title="useSession" icon="badge-check" href="/sdks/web/reference/react/useSession">
    The session a scan updates in place.
  </Card>

  <Card title="Tap capture" icon="scan-line" href="/sdks/web/tap-capture">
    The source priority model and the user-gesture rule.
  </Card>

  <Card title="webNfcPermissionState" icon="lock" href="/sdks/web/reference/tap-sources/webNfcPermissionState">
    Read the origin's scan permission.
  </Card>

  <Card title="WebNfcError" icon="triangle-alert" href="/sdks/web/reference/tap-sources/WebNfcError">
    The typed failures a scan rejects with.
  </Card>
</CardGroup>
