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

# Web SDK

> Integrate the OTPless Headless Web SDK for the authentication flow — install via npm or a script tag, initialize, initiate with a requestId, and handle events.

This page covers the integration of the OTPless Headless Web SDK. The SDK accepts the `requestId` generated by the [Create API](/docs/server-initiated-auth/create-api), performs the authentication, and reports progress through events. Your backend confirms the final result via the [Status Check API](/docs/server-initiated-auth/status-check-api).

<Tabs>
  <Tab title="npm">
    ## Step 1: Install the SDK

    Install the OTPless Headless SDK with your favorite package manager:

    ```bash theme={null}
    npm install otpless-headless-js
    # or
    yarn add otpless-headless-js
    # or
    pnpm add otpless-headless-js
    ```

    <Note>
      Check the latest version of the [SDK on npm](https://www.npmjs.com/package/otpless-headless-js).
    </Note>

    <Warning>
      Make sure your authentication channel is enabled on the [OTPLESS dashboard](https://otpless.com/dashboard/customer/channels).
    </Warning>

    ## Step 2: Initialize the SDK

    Initialize the SDK with your App ID and subscribe to its events. The `useOTPless()` hook is available for React; for any other framework, use the `otpless` object.

    <CodeGroup>
      ```jsx React theme={null}
      import { useEffect } from 'react';
      import { useOTPless, EVENT_TYPES } from 'otpless-headless-js';

      function Login() {
        const { init, initiate, on, ready, loading } = useOTPless();

        useEffect(() => {
          init('YOUR_APP_ID');
          const unsubscribe = on({
            [EVENT_TYPES.FAILED]: onFailed,
            [EVENT_TYPES.INITIATE]: onInitiate,
            [EVENT_TYPES.ONETAP]: onOneTap,
          });
          return () => unsubscribe();
        }, [init, on]);

        // ...
      }
      ```

      ```js JavaScript theme={null}
      import { otpless, EVENT_TYPES } from 'otpless-headless-js';

      await otpless.init('YOUR_APP_ID');

      const unsubscribe = otpless.on({
        [EVENT_TYPES.FAILED]: onFailed,
        [EVENT_TYPES.INITIATE]: onInitiate,
        [EVENT_TYPES.ONETAP]: onOneTap,
      });
      ```
    </CodeGroup>

    <Note>
      Replace `YOUR_APP_ID` with your actual App ID from the [OTPLESS dashboard](https://dashboard.otpless.com/login). For the Next.js App Router, add `'use client';` at the top of the component file.
    </Note>

    <Note>
      Initialization is **not** reported as an event. In React, gate on the
      `ready` flag returned by `useOTPless()`; elsewhere, `await otpless.init()`
      before calling `initiate()`. A failed initialization arrives on `FAILED`
      with `statusCode 5003`.
    </Note>

    ## Step 3: Initiate authentication with the requestId

    Call `initiate()` with your normal authentication payload — `channel` plus the user's `phone`/`countryCode` or `email` — and append the `requestId` returned by the [Create API](/docs/server-initiated-auth/create-api).

    <CodeGroup>
      ```tsx React theme={null}
      import type { OTPlessResponse } from 'otpless-headless-js';

      const startAuth = async () => {
        if (!ready) return;

        const res: OTPlessResponse = await initiate({
          channel: 'PHONE',
          phone: '9876543210',
          countryCode: '+91',
          requestId: 'REQUEST_ID_FROM_API',
        });

        if (!res.success) {
          console.error('initiate failed:', res.response?.errorMessage);
        }
      };
      ```

      ```js JavaScript theme={null}
      const res = await otpless.initiate({
        channel: 'PHONE',
        phone: '9876543210',
        countryCode: '+91',
        requestId: 'REQUEST_ID_FROM_API',
      });

      if (!res.success) {
        console.error('initiate failed:', res.response?.errorMessage);
      }
      ```
    </CodeGroup>

    <Note>
      The `requestId` is **appended to** your usual payload — it does not replace
      any field. Keep sending `channel` and the identity exactly as you do for a
      standard headless integration.
    </Note>

    <Warning>
      Send the **same identity** you passed to the Create API. The server resolves
      the identity bound to the `requestId` and uses that one, so if the two
      disagree the `requestId` silently wins and the number you passed is ignored.
    </Warning>

    <Note>
      Start polling the [Status Check API](/docs/server-initiated-auth/status-check-api) from your backend immediately after calling `initiate()`. The SDK events and the server status run in parallel.
    </Note>

    ## Step 4: Handle events

    ```js theme={null}
    const onFailed = (event) => {
      // Terminal failure — initialization failed, or authentication was
      // attempted and then failed / expired.
      if (event.statusCode === 5003) {
        // Initialization failed. Call init() again.
      }
      // Rely on the Status Check API for the authoritative error.
    };

    const onInitiate = (event) => {
      // Authentication has been initiated
      if (event.statusCode !== 200) {
        handleInitiateError(event);
      } else {
        // Authentication is in progress — show a loading state.
      }
    };

    const onOneTap = (event) => {
      // Final success response — returns token / idToken
      const { token, idToken } = event.response ?? {};
      // Verify the token on your backend.
    };
    ```
  </Tab>

  <Tab title="Script Tag">
    ## Step 1: Add the SDK script

    Add the OTPLESS SDK to your project by including the following script in the `<head>` section of your HTML document:

    ```html index.html theme={null}
    <script
      id="otpless-sdk"
      src="https://otpless.com/v4.3/headless.js"
      data-appid="YOUR_APP_ID"
    ></script>
    ```

    <Warning>
      Use the **v4.3** path — `/v4/headless.js` will not work for this flow. It is
      not an older build of the same SDK: the `v4` bundle ships a different
      headless implementation with no `requestId` support at all. The `requestId`
      field exists only on the `v4.1`+ builds, and v4.3 is the current one.

      Keep `id="otpless-sdk"` too — the SDK reads both its App ID and its version
      from that element.
    </Warning>

    <Note>
      Replace `YOUR_APP_ID` with your actual App ID from the [OTPLESS dashboard](https://dashboard.otpless.com/login).
    </Note>

    <Warning>
      Make sure your authentication channel is enabled on the [OTPLESS dashboard](https://otpless.com/dashboard/customer/channels).
    </Warning>

    ## Step 2: Initialize the SDK and register the callback

    Create an `OTPless` instance with a callback. Every event is delivered to this callback with its `responseType`:

    ```html index.html theme={null}
    <script>
      const callback = (event) => {
        const EVENTS_MAP = {
          FAILED: onFailed,
          INITIATE: onInitiate,
          ONETAP: onOneTap,
        };

        if ("responseType" in event) EVENTS_MAP[event.responseType]?.(event);
      };

      // Initialize the OTPLESS SDK with the callback.
      const OTPlessSignin = new OTPless(callback);
    </script>
    ```

    <Note>
      Initialization is **not** reported as an event on this integration. Use the
      `OTPlessSignin.isReady()` method to check whether the SDK has finished
      initializing before calling `initiate()`; a failed initialization arrives on
      the `FAILED` callback with `statusCode 5003`.
    </Note>

    ## Step 3: Call initiate() with the requestId

    Call `initiate()` with your normal authentication payload — `channel` plus the user's `phone`/`countryCode` or `email` — and append the `requestId` returned by the [Create API](/docs/server-initiated-auth/create-api).

    ```js theme={null}
    const startAuth = async () => {
      const res = await OTPlessSignin.initiate({
        channel: "PHONE",
        phone: "9876543210",
        countryCode: "+91",
        requestId: "REQUEST_ID_FROM_API",
      });

      if (!res.success) {
        console.error("initiate failed:", res.response?.errorMessage);
      }
    };
    ```

    <Note>
      The `requestId` is **appended to** your usual payload — it does not replace
      any field. Keep sending `channel` and the identity exactly as you do for a
      standard headless integration.
    </Note>

    <Warning>
      Send the **same identity** you passed to the Create API. The server resolves
      the identity bound to the `requestId` and uses that one, so if the two
      disagree the `requestId` silently wins and the number you passed is ignored.
    </Warning>

    <Note>
      Start polling the [Status Check API](/docs/server-initiated-auth/status-check-api) from your backend immediately after calling `initiate()`. The SDK events and the server status run in parallel.
    </Note>

    ## Step 4: Handle events in the callback

    ```js theme={null}
    const onFailed = (event) => {
      // Terminal failure — initialization failed, or authentication was
      // attempted and then failed / expired.
      if (event.statusCode === 5003) {
        // Initialization failed. Re-create the OTPless instance.
      }
      // Rely on the Status Check API for the authoritative error.
    };

    const onInitiate = (event) => {
      // Authentication has been initiated
      if (event.statusCode !== 200) {
        handleInitiateError(event);
      } else {
        // Authentication is in progress — show a loading state.
      }
    };

    const onOneTap = (event) => {
      // Final success response — returns token / idToken
      const { token, idToken } = event.response ?? {};
      // Verify the token on your backend.
    };
    ```
  </Tab>
</Tabs>

## Response type

`initiate()` and every event deliver an `OTPlessResponse`:

```ts theme={null}
type OTPlessResponse = {
  responseType: string;
  response?: {
    token?: string;
    idToken?: string;
    errorCode?: string;
    errorMessage?: string;
    [key: string]: any;
  };
  success: boolean;
  statusCode: number;
};
```

## Event reference

These are the `responseType` values the Web SDK delivers to your callback.

| Event | State | Meaning |
| - | - | - |
| `INITIATE` | Non-terminal | Authentication is in progress after pre-checks pass. |
| `ONETAP` | **Success — terminal** | Authentication completed successfully. Returns `token` / `idToken`. |
| `FAILED` | **Failed — terminal** | Terminal failure: the SDK could not initialize (`statusCode 5003`), or authentication was attempted and then failed / expired. |

\| `OTP_AUTO_READ` | Non-terminal | Mobile browsers only, OTP channels only — the OTP was read automatically. Optional to handle. |

<Note>
  **Readiness is not an event on the web.** Unlike the Android and iOS SDKs,
  the Web SDK emits no `SDK_READY` callback — check initialization with
  `isReady()` on the script tag, or the `ready` flag / `await init()` on npm.
  There is likewise no separate `AUTH_TERMINATED` event; terminal
  authentication failures arrive on `FAILED`.
</Note>

<Note>
  This table covers the events relevant to the server-initiated flow. For the
  Web SDK's full event and method surface, see the
  [Headless Web SDK](/docs/frontend-sdks/web-sdks/javascript/headless) reference.
</Note>

<Warning>
  `FAILED` is a terminal failure. Always treat the [Status Check API](/docs/server-initiated-auth/status-check-api) result as authoritative.
</Warning>

<Note>
  For the `errorCode` / `statusCode` values surfaced in SDK events, see [SDK Error Codes](/docs/server-initiated-auth/sdk-error-codes).
</Note>

## Next step

<Card title="Status Check API" icon="circle-check" href="/docs/server-initiated-auth/status-check-api">
  Confirm the authoritative auth status from your server.
</Card>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.