JavaScript SDK

v1.0.0 Public Key (pk_live_*) ~4KB minified Zero dependencies TypeScript included

The JavaScript SDK (@xident/browser) is a lightweight redirect-based SDK for age verification. It redirects users to verify.xident.io where liveness detection, age bracket recognition, and document verification run, then returns users to your site with a verification token.

Never use your secret key in frontend code. The JS SDK uses your public API key (pk_live_* or pk_test_*). If you pass a secret key (sk_*), the SDK will throw a SecretKeyError to protect you.

How It Works

  1. 1. JS SDK creates an init token — Your site calls Xident.verify(). The SDK sends your public key to the API, receives a signed verification URL, and redirects the user.
  2. 2. User verifies on Xident — Liveness detection, age bracket recognition, and/or document OCR run on verify.xident.io.
  3. 3. Redirect back with token — The user returns to your callback URL with ?token=xxx&status=completed&result=confirmed.
  4. 4. Server verifies with secret key — Your backend calls GET /verify/v1/status/{token} with your secret key (sk_live_*) via the X-API-Key header to confirm the result. Never trust URL params alone.

Installation

npm (for bundlers)

npm install @xident/browser

CDN / Script Tag

<!-- Xident CDN (recommended) -->
<script src="https://sdk.xident.io/xident.min.js"></script>

<!-- unpkg -->
<script src="https://unpkg.com/@xident/browser/dist/xident.min.js"></script>

<!-- jsDelivr -->
<script src="https://cdn.jsdelivr.net/npm/@xident/browser/dist/xident.min.js"></script>

Quick Start

Script Tag (Zero-Config)

The simplest integration. Add the script tag with data-* attributes and a button:

<!-- Add the script tag with data-* attributes -- auto-configures on load -->
<script
  src="https://sdk.xident.io/xident.min.js"
  data-api-key="pk_live_your_key_here"
  data-callback-url="https://yoursite.com/verified"
></script>

<!-- The SDK exposes window.Xident automatically -->
<button onclick="Xident.verify()">Verify My Age</button>

<script>
  // On your callback page, extract the result:
  const result = Xident.getVerificationFromUrl();
  if (result) {
    // IMPORTANT: verify the token server-side!
    fetch('/api/verify?token=' + result.token);
  }
</script>

ES Module / Bundler

import { Xident } from '@xident/browser';

// 1. Configure
Xident.configure({
  apiKey: 'pk_live_your_key_here',
  callbackUrl: 'https://yoursite.com/verified',
});

// 2. Start verification (redirects user to verify.xident.io)
await Xident.verify();

// 3. On callback page -- extract and verify server-side
const result = Xident.getVerificationFromUrl();
if (result) {
  console.log(result.token);   // verification token
  console.log(result.status);  // 'completed' | 'failed'
  console.log(result.result);  // 'confirmed' | 'rejected'
}

Configuration

The SDK can be configured via script tag data-* attributes (auto-init on DOM ready) or programmatically via Xident.configure().

Script Tag Attributes

<script
  src="https://sdk.xident.io/xident.min.js"
  data-api-key="pk_live_your_key_here"
  data-callback-url="https://yoursite.com/verified"
  data-success-url="https://yoursite.com/success"
  data-failed-url="https://yoursite.com/denied"
  data-min-age="21"
  data-theme="dark"
  data-locale="de"
  data-user-id="user-123"
  data-liveness-difficulty="medium"
  data-verify-url="https://verify.xident.io"
  data-api-url="https://api.xident.io/verify/v1"
></script>

Programmatic Configuration

Xident.configure({
  // Required
  apiKey: 'pk_live_your_key_here',

  // Callback mechanism (at least ONE is required)
  callbackUrl: 'https://yoursite.com/verified',       // Single callback URL
  // OR
  successUrl: 'https://yoursite.com/success',          // Redirect on pass
  failedUrl: 'https://yoursite.com/denied',            // Redirect on fail
  // OR
  onVerified: (result) => console.log('Passed!', result),  // JS callback
  onFailed: (result) => console.log('Failed!', result),    // JS callback

  // Optional
  minAge: 18,                        // Age threshold (default: 18)
  theme: 'auto',                     // 'light' | 'dark' | 'auto'
  locale: 'en',                      // Language code
  userId: 'user-123',                // Your internal user ID
  livenessDifficulty: 'medium',      // 'easy' (1 action) | 'medium' (2) | 'hard' (3)
  verifyUrl: 'https://verify.xident.io',                 // Custom widget URL
  apiUrl: 'https://api.xident.io/verify/v1',             // Custom API URL
});

Configuration Options

Option Type Default Description
apiKey string Required. Public API key (pk_live_* or pk_test_*).
callbackUrl string Single callback URL for all outcomes. At least one callback mechanism required.
successUrl string Redirect URL on successful verification. Use with failedUrl.
failedUrl string Redirect URL on failed verification. Use with successUrl.
onVerified (result) => void JS callback fired before redirect on success.
onFailed (result) => void JS callback fired before redirect on failure.
minAge number 18 Minimum age threshold (12, 15, 18, 21, 25).
theme string Widget theme: 'light', 'dark', or 'auto'.
locale string Language code: en, es, de, fr, it, pt, nl, pl, tr, ar, ja, ko.
livenessDifficulty string Challenge difficulty: 'easy' (1 action), 'medium' (2), 'hard' (3).
userId string Auto UUIDv7 Your internal user ID. Auto-generated and persisted in localStorage if omitted.
verifyUrl string https://verify.xident.io Custom verification widget URL.
apiUrl string https://api.xident.io/verify/v1 Custom API URL (must be a Xident domain or localhost).

At least one callback mechanism is required: callbackUrl, successUrl + failedUrl, or onVerified/onFailed.

Verification

Starting Verification

Xident.verify(options?) creates an init token via the API, validates the returned URL, and redirects the user (or opens a new tab).

// Basic -- redirect user to verification widget
await Xident.verify();

// With user tracking
await Xident.verify({
  userId: 'user-123',
});

// With metadata (available in session response, max 1KB encoded)
await Xident.verify({
  userId: 'user-123',
  metadata: {
    orderId: 'order-456',
    plan: 'premium',
  },
});

// Open in new tab instead of redirecting
await Xident.verify({
  newTab: true,
});
Option Type Description
userId string Override the config-level userId. Auto-generated UUIDv7 if omitted everywhere.
metadata Record<string, string> Arbitrary key-value pairs. Base64-encoded and sent with the init request. Max 1KB encoded.
newTab boolean Open verification in a new tab with noopener,noreferrer. Default: false.

Parsing the Callback

Xident.getVerificationFromUrl() extracts the verification result from the current URL query parameters. Returns null if no token parameter is present.

// On your callback page -- extract verification result from URL
// URL will look like: https://yoursite.com/verified?token=xxx&status=completed&result=confirmed
const result = Xident.getVerificationFromUrl();

if (result) {
  console.log(result.token);   // Verification token (use for server-side check)
  console.log(result.status);  // Session status: 'completed', 'failed', etc.
  console.log(result.result);  // Outcome: 'confirmed' or 'rejected'

  // CRITICAL: Always verify server-side with your secret key!
  const response = await fetch(`/api/verify?token=${result.token}`);
  const serverResult = await response.json();

  if (serverResult.verified) {
    // Grant access
  }
}

CRITICAL: Never trust client-side URL parameters for authorization decisions. Always verify the token on your backend using your secret API key (sk_live_*) via GET /verify/v1/status/{token}.

Auto-Callback Processing

When using the script tag with auto-init, the SDK automatically calls processCallback() on page load. If the URL contains verification parameters, it fires your onVerified/onFailed callbacks and redirects to successUrl/failedUrl.

// When using script tag + successUrl/failedUrl or onVerified/onFailed,
// the SDK auto-processes callbacks on page load.
//
// If the URL contains ?token=xxx&result=confirmed:
//   1. onVerified(result) fires (if configured)
//   2. User is redirected to successUrl (if configured)
//
// If the URL contains ?token=xxx&result=rejected:
//   1. onFailed(result) fires (if configured)
//   2. User is redirected to failedUrl (if configured)

Xident.configure({
  apiKey: 'pk_live_xxx',
  successUrl: 'https://yoursite.com/success',
  failedUrl: 'https://yoursite.com/denied',
  onVerified: (result) => {
    // Fires before redirect -- use for analytics
    analytics.track('verification_passed', { token: result.token });
  },
  onFailed: (result) => {
    analytics.track('verification_failed', { token: result.token });
  },
});

Helper Methods

// Check if SDK is configured and ready
if (Xident.isConfigured()) {
  await Xident.verify();
}

// Get current configuration (read-only copy)
const config = Xident.getConfig();
// config?.apiKey     -> 'pk_live_...'
// config?.minAge     -> 18 (defaults applied)
// config?.theme      -> 'dark'

// SDK version
console.log(Xident.version); // '1.0.0'
Method / Property Returns Description
Xident.verify(options?) Promise<void> Create init token and redirect user to verification widget.
Xident.start(options?) Promise<void> Deprecated alias for verify().
Xident.configure(config) void Set SDK configuration. Validates API key and URLs.
Xident.isConfigured() boolean true if API key and at least one callback mechanism are set.
Xident.getConfig() XidentConfig | null Read-only copy of the current config. Returns null if unconfigured.
Xident.getVerificationFromUrl() VerificationResult | null Parse ?token=&status=&result= from current URL. Returns null if no token.
Xident.getSessionFromUrl() VerificationResult | null Deprecated alias for getVerificationFromUrl().
Xident.version string SDK version (currently "1.0.0").

Error Handling

All SDK errors extend XidentError. You can catch specific error types for granular handling:

import {
  XidentError,       // Base class for all SDK errors
  ConfigError,       // Missing or invalid configuration
  SecretKeyError,    // Secret key used in browser (BLOCKED)
  InvalidUrlError,   // Invalid URL format
  ApiError,          // API request failed (has statusCode, errorCode)
  TimeoutError,      // Request timed out (10s default)
  RateLimitError,    // verify() called too rapidly (2s cooldown)
  MetadataTooLargeError,  // Metadata exceeds 1KB encoded
} from '@xident/browser';

try {
  await Xident.verify({ metadata: { huge: 'x'.repeat(2000) } });
} catch (error) {
  if (error instanceof SecretKeyError) {
    // You used sk_live_... in the browser! Use pk_live_... instead
  } else if (error instanceof ConfigError) {
    // SDK not configured, or missing callback mechanism
  } else if (error instanceof InvalidUrlError) {
    // callbackUrl/successUrl/failedUrl is not a valid URL
  } else if (error instanceof ApiError) {
    console.log(error.statusCode);  // HTTP status (401, 400, 500, etc.)
    console.log(error.errorCode);   // API error code string
  } else if (error instanceof TimeoutError) {
    // Network request timed out
  } else if (error instanceof RateLimitError) {
    // Called verify() again within 2 seconds
  } else if (error instanceof MetadataTooLargeError) {
    console.log(error.size);     // Actual size in bytes
    console.log(error.maxSize);  // Max allowed (1024)
  } else if (error instanceof XidentError) {
    // Catch-all for any SDK error
  }
}
Error Class When Thrown Key Properties
XidentError Base class for all SDK errors message
ConfigError Missing apiKey, missing callback mechanism, or SDK not configured message
SecretKeyError Secret key (sk_*) used in browser code message
InvalidUrlError callbackUrl/successUrl/failedUrl is not a valid HTTPS URL (localhost allowed for dev) message
ApiError API returned an error response statusCode, errorCode
TimeoutError API request timed out (10s) message
RateLimitError verify() called again within 2 seconds message
MetadataTooLargeError Metadata exceeds 1KB when base64-encoded size, maxSize

Framework Examples

React

// components/XidentVerifyButton.tsx
import { useEffect, useState, useCallback } from 'react';
import { Xident } from '@xident/browser';
import type { VerificationResult } from '@xident/browser';

interface Props {
  apiKey: string;
  callbackUrl: string;
  minAge?: number;
  userId?: string;
  theme?: 'light' | 'dark' | 'auto';
  locale?: string;
}

export function XidentVerifyButton({
  apiKey, callbackUrl, minAge = 18, userId, theme, locale,
}: Props) {
  const [result, setResult] = useState<VerificationResult | null>(null);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    Xident.configure({ apiKey, callbackUrl, minAge, userId, theme, locale });

    // Check if returning from verification
    const verificationResult = Xident.getVerificationFromUrl();
    if (verificationResult) {
      setResult(verificationResult);
    }
  }, [apiKey, callbackUrl, minAge, userId, theme, locale]);

  const handleVerify = useCallback(async () => {
    try {
      setError(null);
      await Xident.verify();
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Verification failed');
    }
  }, []);

  if (result) {
    return (
      <div>
        <h3>Verification Complete</h3>
        <p>Status: {result.status}</p>
        <p>Result: {result.result}</p>
        <p><strong>Important:</strong> Verify the token server-side!</p>
      </div>
    );
  }

  return (
    <div>
      <button onClick={handleVerify}>Verify My Age</button>
      {error && <p style={{ color: 'red' }}>{error}</p>}
    </div>
  );
}

Next.js (App Router)

Use 'use client' since the SDK runs in the browser only. Verify tokens in a Route Handler or Server Action using the Node.js SDK.

// components/XidentVerify.tsx
'use client';

import { useEffect, useState, useCallback } from 'react';
import { Xident } from '@xident/browser';
import type { VerificationResult } from '@xident/browser';

export default function XidentVerify() {
  const [result, setResult] = useState<VerificationResult | null>(null);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    Xident.configure({
      apiKey: process.env.NEXT_PUBLIC_XIDENT_API_KEY || 'pk_test_xxx',
      callbackUrl: `${window.location.origin}/verify/callback`,
    });

    const verificationResult = Xident.getVerificationFromUrl();
    if (verificationResult) {
      setResult(verificationResult);
      // In production, verify token server-side:
      // fetch(`/api/verify?token=${verificationResult.token}`)
    }
  }, []);

  const handleVerify = useCallback(async () => {
    try {
      setError(null);
      await Xident.verify();
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Verification failed');
    }
  }, []);

  if (result) {
    return (
      <div className="p-6 max-w-md mx-auto">
        <h2 className="text-xl font-bold mb-4">Verification Complete</h2>
        <p>Status: {result.status}</p>
        <p>Result: {result.result}</p>
        <p className="mt-4 text-sm text-amber-600">
          Always verify the token server-side via your API route.
        </p>
      </div>
    );
  }

  return (
    <div className="p-6 max-w-md mx-auto">
      <button
        onClick={handleVerify}
        className="px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700"
      >
        Verify My Age
      </button>
      {error && <p className="mt-2 text-red-600">{error}</p>}
    </div>
  );
}

Vue 3

<!-- components/XidentVerify.vue -->
<template>
  <div>
    <div v-if="result">
      <h3>Verification Complete</h3>
      <p>Status: {{ result.status }}</p>
      <p>Result: {{ result.result }}</p>
      <p>
        <strong>Important:</strong> Verify the token server-side via
        <code>GET /verify/v1/status/{{ result.token }}</code>
      </p>
    </div>
    <div v-else>
      <button @click="handleVerify">Verify My Age</button>
      <p v-if="error" style="color: red">{{ error }}</p>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { Xident } from '@xident/browser';
import type { VerificationResult } from '@xident/browser';

const props = defineProps<{
  apiKey: string;
  callbackUrl: string;
  minAge?: number;
  userId?: string;
  theme?: 'light' | 'dark' | 'auto';
  locale?: string;
}>();

const result = ref<VerificationResult | null>(null);
const error = ref<string | null>(null);

onMounted(() => {
  Xident.configure({
    apiKey: props.apiKey,
    callbackUrl: props.callbackUrl,
    minAge: props.minAge ?? 18,
    userId: props.userId,
    theme: props.theme,
    locale: props.locale,
  });

  const verificationResult = Xident.getVerificationFromUrl();
  if (verificationResult) {
    result.value = verificationResult;
  }
});

async function handleVerify() {
  try {
    error.value = null;
    await Xident.verify();
  } catch (err) {
    error.value = err instanceof Error ? err.message : 'Verification failed';
  }
}
</script>

Response Types

VerificationResult

Returned by getVerificationFromUrl(). Parsed from the callback URL query parameters.

Field Type Description
token string Verification token. Use this for server-side verification.
status string Session status: 'completed', 'failed', etc.
result string Verification outcome: 'confirmed' or 'rejected'.

TypeScript

Full type definitions are included with the package. If using the CDN/script tag, declare the global type:

// Exported from '@xident/browser'

interface XidentConfig {
  apiKey: string;                                // Required: pk_live_* or pk_test_*
  apiUrl?: string;                               // Default: https://api.xident.io/verify/v1
  successUrl?: string;                           // Redirect on pass
  failedUrl?: string;                            // Redirect on fail
  callbackUrl?: string;                          // Single callback URL
  verifyUrl?: string;                            // Default: https://verify.xident.io
  theme?: 'light' | 'dark' | 'auto';
  locale?: string;                               // e.g. 'en', 'de', 'fr'
  minAge?: number;                               // Default: 18
  livenessDifficulty?: 'easy' | 'medium' | 'hard';
  userId?: string;                               // Your internal user ID
  onVerified?: (result: VerificationResult) => void;
  onFailed?: (result: VerificationResult) => void;
}

interface VerifyOptions {
  userId?: string;                               // Override config userId
  metadata?: Record<string, string>;             // Max 1KB encoded
  newTab?: boolean;                              // Open in new tab (default: false)
}

interface VerificationResult {
  token: string;                                 // Verification token
  status: string;                                // 'completed', 'failed', etc.
  result: string;                                // 'confirmed' or 'rejected'
}

interface XidentInstance {
  verify(options?: VerifyOptions): Promise<void>;
  start(options?: VerifyOptions): Promise<void>; // Deprecated alias for verify()
  configure(config: XidentConfig): void;
  isConfigured(): boolean;
  getConfig(): Readonly<XidentConfig> | null;
  getVerificationFromUrl(): VerificationResult | null;
  getSessionFromUrl(): VerificationResult | null; // Deprecated
  version: string;
}

Environment Variables

Variable Where Description
NEXT_PUBLIC_XIDENT_API_KEY Next.js frontend Public API key for browser use (pk_live_*).
VITE_XIDENT_API_KEY Vite frontend Public API key for browser use (pk_live_*).

Tip: Public keys are safe to expose in frontend code. They can only create init tokens, not read verification results.

Testing & Development

<!-- Test mode: http://localhost is allowed for callback URLs -->
<script
  src="https://sdk.xident.io/xident.min.js"
  data-api-key="pk_test_xxx"
  data-callback-url="http://localhost:3000/verified"
></script>

<script>
  // Debug helpers
  console.log('Version:', Xident.version);       // '1.0.0'
  console.log('Ready:', Xident.isConfigured());   // true/false
  console.log('Config:', Xident.getConfig());     // { apiKey, minAge: 18, ... }
</script>

Test API keys (pk_test_*) work with http://localhost callback URLs. Use them for development and CI.

Security

  1. HTTPS required — Callback URLs must use HTTPS in production. http://localhost is allowed for development.
  2. Server-side verification — Never trust client-side URL parameters. Always verify the token on your backend.
  3. Secret key protection — Keep sk_live_* on your server. The SDK blocks sk_* keys with a SecretKeyError.
  4. Domain validation — The SDK only allows API requests to Xident domains and localhost.
  5. Rate limitingverify() enforces a 2-second cooldown between calls.
  6. Metadata limits — Metadata is capped at 1KB (base64-encoded) to prevent abuse.

Architecture

Your Website                           Xident Platform
┌────────────────────────────┐                   ┌───────────────────────┐
│ @xident/browser (~4KB)       │  ──redirect──▶  │ verify.xident.io        │
│                              │                   │                         │
│ 1. User clicks "Verify"      │                   │ 2. Liveness detection    │
│ 2. SDK calls POST /init      │                   │ 3. Age bracket check     │
│ 3. Gets signed verify_url    │                   │ 4. Document OCR (if needed)│
│ 4. Redirects user            │                   │                         │
└────────────────────────────┘                   └───────────────────────┘
┌────────────────────────────┐                            │
│ Callback URL                 │  ◀──redirect + ?token=xxx──┘
│                              │
│ 5. getVerificationFromUrl()   │
│ 6. Verify token server-side   │
│ 7. Grant or deny access       │
└────────────────────────────┘

The SDK itself performs no verification logic. It is purely a redirect orchestrator (~4KB). All ML models, liveness detection, and document processing run on the Xident platform.

Browser Support

Browser Minimum Version
Chrome60+
Firefox55+
Safari12+
Edge79+

The SDK targets ES2015 and uses only standard browser APIs (URL, URLSearchParams, btoa, crypto.getRandomValues, fetch).

Related