otpless-headless-js
v1.2.0
Published
OTPless Headless SDK for JS framework.
Maintainers
Readme
OTPless Headless
A lightweight JavaScript SDK wrapper for OTPless Headless that provides seamless OTP authentication with WhatsApp, SMS, and email. Works with React, Vue.js, Next.js, and any JavaScript framework.
Features
- 🚀 Tiny Bundle - Only 3.6KB minified
- 🎯 Framework Agnostic - Works with React, Vue, Next.js, and any JS framework
- 📱 Phone Authentication - SMS OTP with country code support
- 🔄 Loading States - Built-in loading management
- 🎪 Event System - Listen to authentication events
- 🛡️ Type Safe - Full TypeScript support
- 🌐 SSR Ready - Works with Next.js and other SSR frameworks
Demo
Explore the live demo repository to see OTPless Headless in action:
https://github.com/otpless-tech/headless-demo
Installation
npm install otpless-headless-js
# or
yarn add otpless-headless-js
# or
pnpm add otpless-headless-jsQuick Start
import { useEffect } from 'react';
import { useOTPless, CHANNELS, EVENT_TYPES } from 'otpless-headless-js';
function MyComponent() {
const { init, initiate, verify, on, loading, ready } = useOTPless();
useEffect(() => {
// Initialize with your OTPless App ID
init('YOUR_APP_ID');
// Listen to a single event
const unsubscribe = on(EVENT_TYPES.OTP_AUTO_READ, (event) => {
const autoOtp = event?.response?.otp ?? '';
console.log('OTP auto-read:', autoOtp);
});
// Listen to multiple events at once
const unsubscribeAll = on({
[EVENT_TYPES.ONETAP]: (event) => {
console.log('Auth success:', event);
},
[EVENT_TYPES.OTP_AUTO_READ]: (event) => {
const autoOtp = event?.response?.otp ?? '';
console.log('OTP auto-read:', autoOtp);
},
[EVENT_TYPES.FALLBACK_TRIGGERED]: (event) => {
// Fired when the delivery channel changes (e.g. WhatsApp → SMS fallback)
console.log('Fallback channel:', event.response?.deliveryChannel);
},
[EVENT_TYPES.FAILED]: (event) => {
console.error('Failed:', event.response?.errorMessage);
},
});
return () => {
unsubscribe();
unsubscribeAll();
};
}, [init, on]);
const initiateAuth = async () => {
if (!ready) {
console.log('SDK not ready yet');
return;
}
try {
const result = await initiate({
channel: CHANNELS.PHONE,
phone: '9876543210',
countryCode: '+91',
});
if (result.success) {
console.log('OTP sent successfully:', result);
} else {
console.error('Failed to send OTP:', result.response?.errorMessage);
}
} catch (error) {
console.error('Failed to send OTP:', error);
}
};
const verifyAuth = async () => {
if (!ready) {
console.log('SDK not ready yet');
return;
}
try {
const result = await verify({
channel: CHANNELS.PHONE,
phone: '9876543210',
otp: '123456',
countryCode: '+91',
});
if (result.success) {
console.log('OTP verified successfully:', result);
// User is authenticated - proceed with your app logic
} else {
console.error('OTP verification failed:', result.response?.errorMessage);
}
} catch (error) {
console.error('Verification failed:', error);
}
};
if (!ready) {
return <div>Loading OTPless SDK...</div>;
}
return (
<div>
<button onClick={initiateAuth} disabled={loading}>
{loading ? 'Sending...' : 'Send OTP'}
</button>
<button onClick={verifyAuth} disabled={loading}>
{loading ? 'Verifying...' : 'Verify OTP'}
</button>
</div>
);
}API Reference
useOTPless()
Returns an object with the following properties:
type UseOTPless = () => {
ready: boolean;
loading: boolean;
init(appId: string): Promise<void>;
initiate(request: InitiateRequest): Promise<OTPlessResponse>;
verify(request: VerifyRequest): Promise<OTPlessResponse>;
on(
event: EventType | Record<EventType, Function>,
callback?: Function
): () => void;
off(event: EventType, callback: Function): void;
};| Property | Type | Description |
| --- | --- | --- |
| ready | boolean | Whether the OTPless SDK script is loaded and ready. |
| loading | boolean | True while an operation is in progress (init/initiate/verify). |
| init(appId) | Promise<void> | Initialize the SDK with your App ID. Call this once. |
| initiate(request) | Promise<OTPlessResponse> | Send OTP (e.g., via CHANNELS.PHONE). |
| verify(request) | Promise<OTPlessResponse> | Verify the OTP received by the user. |
| on(event, callback?) | () => void | Subscribe to events. Accepts a single EventType or a map of { [EventType]: handler }. Returns an unsubscribe function. |
| off(event, callback) | void | Remove a previously registered listener. |
InitiateRequest
| Field | Type | Required | Description |
| --- | --- | --- | --- |
| channel | "PHONE" \| "EMAIL" \| "OAUTH" | Yes | Authentication channel. |
| phone | string | For PHONE | Digits only, no country code. |
| countryCode | string | For PHONE | e.g. "+91" or "91". |
| email | string | For EMAIL | User's email address. |
| metaData | Record<string, any> | No | Custom key-value data attached to the request. Returned as-is in the ONETAP response. Use this to pass tid (template ID) or any other custom fields. |
Passing custom metadata and tid
await initiate({
channel: CHANNELS.PHONE,
phone: '9876543210',
countryCode: '+91',
metaData: {
tid: 'your-template-id', // template ID
source: 'signup-page', // any custom fields
userId: 'abc123',
},
});Types and Constants
This package exports TypeScript types and enum-like constants for safer usage:
import type { InitiateRequest, VerifyRequest, EventType, OTPlessResponse } from 'otpless-headless-js';
import { CHANNELS, EVENT_TYPES } from 'otpless-headless-js';
// Example usage
const req: InitiateRequest = {
channel: CHANNELS.PHONE,
phone: '9876543210',
countryCode: '+91',
};
const onEvent = (type: EventType) => {
if (type === EVENT_TYPES.FAILED) {
console.error('Operation failed');
}
};Response Type
All OTPless API calls (initiate, verify) resolve to the same response shape:
type OTPlessResponsePayload = {
otp?: string;
token?: string;
[key: string]: any;
};
type OTPlessResponse = {
responseType: string;
response?: OTPlessResponsePayload;
success: boolean;
statusCode: number;
};Example:
const res: OTPlessResponse = await initiate({
channel: CHANNELS.PHONE,
phone: '9876543210',
countryCode: '+91',
});
if (res.success) {
console.log('requestId:', res.response?.requestID);
} else {
console.error('initiate failed:', res.response?.errorMessage);
}Framework-Agnostic Usage
This package works with any JavaScript framework. Here's how to use it without React hooks:
import { otpless, CHANNELS, EVENT_TYPES } from 'otpless-headless-js';
// Initialize
await otpless.init('YOUR_APP_ID');
// Send OTP
await otpless.initiate({
channel: CHANNELS.PHONE,
phone: '9876543210',
countryCode: '+91',
});
// Verify OTP
await otpless.verify({
channel: CHANNELS.PHONE,
phone: '9876543210',
countryCode: '+91',
otp: '123456',
});
// Listen to a single event
const unsubscribe = otpless.on(EVENT_TYPES.OTP_AUTO_READ, (event) => {
console.log('OTP auto-read:', event.response?.otp);
});
// Listen to multiple events at once
const unsubscribeAll = otpless.on({
[EVENT_TYPES.ONETAP]: (event) => console.log('Auth Success:', event),
[EVENT_TYPES.OTP_AUTO_READ]: (event) => console.log('Auto OTP:', event.response?.otp),
[EVENT_TYPES.FALLBACK_TRIGGERED]: (event) => console.log('Fallback to:', event.response?.deliveryChannel),
[EVENT_TYPES.FAILED]: (event) => console.error('Failed:', event.response?.errorMessage),
});Events Reference
| Event | EVENT_TYPES constant | When it fires | Key response fields |
| --- | --- | --- | --- |
| ONETAP | EVENT_TYPES.ONETAP | Authentication completed successfully | token, userId |
| OTP_AUTO_READ | EVENT_TYPES.OTP_AUTO_READ | OTP was automatically read from SMS | otp |
| FALLBACK_TRIGGERED | EVENT_TYPES.FALLBACK_TRIGGERED | Delivery channel changed (e.g. WhatsApp → SMS) | deliveryChannel, authType, requestID |
| FAILED | EVENT_TYPES.FAILED | Authentication or SDK initialization failed | errorMessage, errorCode |
| LOADING_STATE | EVENT_TYPES.LOADING_STATE | Loading state changed | loading, sdkLoading, apiPending |
FALLBACK_TRIGGERED in detail
This event fires when the OTPless backend switches the delivery channel mid-flow — for example, when a WhatsApp OTP could not be delivered and the SDK automatically falls back to SMS.
on(EVENT_TYPES.FALLBACK_TRIGGERED, (event) => {
const { deliveryChannel, authType, requestID } = event.response;
// deliveryChannel: the new channel being used (e.g. "SMS")
// authType: the auth type (e.g. "OTP")
// requestID: the original request token
console.log(`Switched to ${deliveryChannel}`);
});Utility Functions
import { normalizeCountryCode, digitsOnly, redactTokens } from 'otpless-headless-js';
// Normalize country code
normalizeCountryCode('91'); // '+91'
normalizeCountryCode('+1-234'); // '+1234'
// Extract digits only
digitsOnly('98-765-43210'); // '9876543210'
// Redact sensitive data for logging
const safeData = redactTokens({
phone: '9876543210',
otp: '123456',
token: 'secret'
});
// { phone: '9876543210', otp: '***redacted***', token: '***redacted***' }Next.js Usage
'use client'; // Required for client components
import { useOTPless } from 'otpless-headless-js';
export default function AuthPage() {
const { ready, loading, init, initiate, verify } = useOTPless();
// ... rest of your component
{{ ... }}License
MIT
Support
For issues and questions, please visit the GitHub repository or contact [email protected].
