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

# Headless SDK

> Use the OwnID headless SDK to drive authentication flows with your own UI.

The **headless SDK** lets you run the full OwnID authentication flow without any pre-built UI components.\
You control the UI; the SDK handles discovery, passkeys, verifications, and session creation.

<Info>
  Headless integration is currently available only for custom Integrations.
</Info>

[Prefer to start from code? Skip to the full example.](#full-example)

***

## Initialize the headless client

Begin by loading the OwnID WebSDK:

```javascript JavaScript theme={null}
<script>
    ((o,w,n,i,d)=>{o[i]=o[i]||((...a) =>new Promise((r, j)=>((o[i].q = o[i].q || []).push([{r,j,_isResolvers:true},...a])))),
    (d=w.createElement("script")).src='https://cdn.ownid.com/sdk/'+n,d.async=1,w.head.appendChild(d);o[i].whenReady = o[i]('init')})
    (window,document,'<appID>','ownid');
</script>
```

Once loaded, get the `headless` instance and bind it to the user’s identifier (`loginId`).

```ts theme={null}
const { headless } = window.ownid;

const hOwnid = headless.withContext({
    loginId: 'user@example.com',
    // or a phone number:
    // loginId: '+15551234567',
});
```

Notes:

* `window.ownid` is available after loading the OwnID script.
* `withContext` returns a new bound instance of the SDK to a specific context with a `loginId`.

***

## Discover available operations

Ask the SDK what authentication options exist for this user in this environment.

```ts theme={null}
const discover = await hOwnid.auth.discover();

if ('error' in discover) throw discover;

// discover.operations may include:
// - PasskeyAuth
// - EmailVerification
// - PhoneNumberVerification
```

You’ll use `discover.operations` to choose the flow order.

***

## Passkey Authentication

If the device supports WebAuthn and this user has a passkey, try that first.

```ts theme={null}
if (
    discover.operations.some((c) => c.type === 'PasskeyAuth') &&
    (await hOwnid.passkeys.auth.isAvailable())
) {
    const passkeyAuthCtrl = await hOwnid.passkeys.auth.start(); 
    if ('error' in passkeyAuthCtrl) throw passkeyAuthCtrl;

    const passkeyAuthRes = await passkeyAuthCtrl.whenSettled;

    if ('error' in passkeyAuthRes) {
        console.log('passkey auth error:', passkeyAuthRes);
        console.log('fallback, e.g. email/phone verification');
    } else {
        return login('PasskeyAuth', passkeyAuthRes);
    }
}
```

What’s happening:

* Find out if we expect passkey authentication to be available for the user if it's listed in `discover.operations`.
* `isAvailable()` to confirm browser support.
* `start()` triggers the passkey challenge.
* When the challenge settles - `whenSettled` resolves to success or error.
* On success, we'll attempt to create a session via `login(...)`.
* On error, we can choose how to handle, e.g. fall back to email/phone.

***

## Email Verification

<Info>
  Notice that in order for Email Verification to be available - your OwnID app needs to be configured with an SMTP provider for sending emails.
</Info>

```ts theme={null}
if (discover.operations.some((c) => c.type === 'EmailVerification')) {

    const challenge = await hOwnid.verifications.email.start();

    // TODO: we use our own UI to collect the One Time Passcode (OTP).
    const otp = prompt(`Check your email for an OTP:`);

    const res = await challenge.complete({ otp });

    if ('error' in res) throw res;
    else return login('EmailVerification', res);
}
```

***

## Phone Verification

<Info>
  Notice that in order for Phone Number Verification to be available - your OwnID app needs to be configured with an SMS provider for sending SMS messages.
</Info>

```ts theme={null}
if (discover.operations.some((c) => c.type === 'PhoneNumberVerification')) {

    const challenge = await hOwnid.verifications.phone.start();

    // TODO: we use our own UI to collect the One Time Passcode (OTP).
    const otp = prompt(`Check your phone for an OTP:`);

    const res = await challenge.complete({ otp });

    if ('error' in res) throw res;
    else return login('PhoneNumberVerification', res);
}
```

***

## Establish a Session

```ts theme={null}
async function login(auth, { accessToken }) {
    const session = await hOwnid.withContext({ accessToken }).auth.login();

    if ('error' in session) throw session;

    const { sessionPayload } = session;

    // TODO: establish your session.
}
```

Notes:

* After a successful login, estbalish your session with the `sessionPayload` you've returned from the [`/getSession` integration endpoint](http://localhost:3000/building-blocks/build-server-endpoints#endpoint-3-get-session) .

***

## Recommended: Post-login Passkey Enrollment

```ts theme={null}
async function login(auth, { accessToken }) {
    const session = await hOwnid.withContext({ accessToken }).auth.login();
    if ('error' in session) throw session;
    const { sessionPayload } = session;
    // TODO: establish your session.

    if (auth !== 'PasskeyAuth' && (await hOwnid.passkeys.enroll.isAvailable())) {
        const passkeyEnrollCtrl = await hOwnid.withContext(session).passkeys.enroll.start();

        if ('error' in passkeyEnrollCtrl) {
            console.log('ignoring start enroll error:', passkeyEnrollCtrl);
            return;
        }

        const passkeyEnrollRes = await passkeyEnrollCtrl.whenSettled;

        if ('error' in passkeyEnrollRes) {
            console.log('ignoring enroll error:', passkeyEnrollRes);
            return;
        }
    }
}
```

Notes:

* As passkey enrollment should be optional - failures should not affect the user-experience.

***

## Full example

```ts theme={null}
const { headless } = window.ownid;

const hOwnid = headless.withContext({
    loginId: 'user@example.com',
    // or a phone number:
    // loginId: '+15551234567',
});

async function authenticate() {
    // Discover available operations
    const discover = await hOwnid.auth.discover();
    if ('error' in discover) throw discover;

    // Try passkey authentication (if available)
    if (
        discover.operations.some((c) => c.type === 'PasskeyAuth') &&
        (await hOwnid.passkeys.auth.isAvailable())
    ) {
        const passkeyAuthCtrl = await hOwnid.passkeys.auth.start();
        if ('error' in passkeyAuthCtrl) throw passkeyAuthCtrl;

        const passkeyAuthRes = await passkeyAuthCtrl.whenSettled;

        if ('error' in passkeyAuthRes) {
            console.log('passkey auth error:', passkeyAuthRes);
            console.log('fallback, e.g. email/phone verification');
        } else {
            return await login('PasskeyAuth', passkeyAuthRes);
        }
    }

    // Email verification flow
    if (discover.operations.some((c) => c.type === 'EmailVerification')) {
        const challenge = await hOwnid.verifications.email.start();
        let res;
        while (true) {
            // TODO: Your own UI to collect the One Time Passcode (OTP).
            const otp = prompt(`Check your email for an OTP:`);

            res = await challenge.complete({ otp });
            if ('error' in res) {
                switch (res.error.code) {
                    case "verification_code_wrong":
                        continue;
                    case "maximum_attempts_reached":
                    default:
                        break;
                }
            }
        }

        if ('error' in res) throw res;
        else return await login('EmailVerification', res);
    }

    // Phone verification flow
    if (discover.operations.some((c) => c.type === 'PhoneNumberVerification')) {
        const challenge = await hOwnid.verifications.phone.start();

        // TODO: we use our own UI to collect the One Time Passcode (OTP).
        const otp = prompt(`Check your phone for an OTP:`);

        const res = await challenge.complete({ otp });

        if ('error' in res) throw res;
        else return await login('PhoneNumberVerification', res);
    }
}

async function login(auth, { accessToken }) {
    const session = await hOwnid.withContext({ accessToken }).auth.login();
    if ('error' in session) throw session;

    const { sessionPayload } = session;

    // TODO: establish your session with sessionPayload from /getSession endpoint

    // Recommended: Enroll a passkey after a non-passkey login
    if (auth !== 'PasskeyAuth' && (await hOwnid.passkeys.enroll.isAvailable())) {
        const passkeyEnrollCtrl = await hOwnid.withContext(session).passkeys.enroll.start();

        if ('error' in passkeyEnrollCtrl) {
            console.log('ignoring start enroll error:', passkeyEnrollCtrl);
            return;
        }

        const passkeyEnrollRes = await passkeyEnrollCtrl.whenSettled;

        if ('error' in passkeyEnrollRes) {
            console.log('ignoring enroll error:', passkeyEnrollRes);
            return;
        }
    }
}
```
