JavaScript SDK
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. 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. User verifies on Xident — Liveness detection, age bracket recognition, and/or document OCR run on
verify.xident.io. - 3. Redirect back with token — The user returns to your callback URL with
?token=xxx&status=completed&result=confirmed. - 4. Server verifies with secret key — Your backend calls
GET /verify/v1/status/{token}with your secret key (sk_live_*) via theX-API-Keyheader 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
- HTTPS required — Callback URLs must use HTTPS in production.
http://localhostis allowed for development. - Server-side verification — Never trust client-side URL parameters. Always verify the token on your backend.
- Secret key protection — Keep
sk_live_*on your server. The SDK blockssk_*keys with aSecretKeyError. - Domain validation — The SDK only allows API requests to Xident domains and localhost.
- Rate limiting —
verify()enforces a 2-second cooldown between calls. - 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 |
|---|---|
| Chrome | 60+ |
| Firefox | 55+ |
| Safari | 12+ |
| Edge | 79+ |
The SDK targets ES2015 and uses only standard browser APIs (URL, URLSearchParams, btoa, crypto.getRandomValues, fetch).
Related
- All SDKs — Overview of all Xident SDKs
- Node.js SDK — Server-side token verification and webhook handling
- Python SDK — Server-side Python SDK with sync and async clients
- Quick Start — End-to-end 5-minute setup
- API Reference — Full REST API documentation