Phone Module
The Phone module verifies a user's phone number via SMS OTP (One-Time Password).
This guide is specific to Web SDK 2.0. If you are still using 1.x, you can find documentation here. We strongly recommend upgrading - contact your Incode Representative for upgrade information.
The Phone module verifies a user's phone number via SMS OTP (One-Time Password).
Follows the form-based pattern (with the OTP sub-loop). See that page for the shared manager lifecycle and skeleton; the rest of this page covers Phone-specific config, states, and methods.
Tag
<incode-phone> is a standard Web Component. Importing the UI subpath registers the custom element; importing the CSS applies the module's styles.
import '@incodetech/web/phone';
import '@incodetech/web/phone/styles.css';Properties
Set these as JavaScript properties on the element (not as HTML attributes):
| Property | Type | Required | Description |
|---|---|---|---|
config | PhoneConfig | ✅ | Configuration options |
onFinish | () => void | ❌ | Called when verification completes |
onError | (error: string) => void | ❌ | Called when an error occurs |
Usage
Vanilla HTML / TypeScript
<incode-phone></incode-phone>
<script type="module">
import { setup } from '@incodetech/core';
import '@incodetech/web/phone';
import '@incodetech/web/phone/styles.css';
await setup({
apiURL: 'https://demo-api.incodesmile.com',
token: 'your-session-token',
});
const phone = document.querySelector('incode-phone');
phone.config = {
otpVerification: true,
otpExpirationInMinutes: 5,
prefill: false,
};
phone.onFinish = () => console.log('Phone verified!');
phone.onError = (err) => console.error('Phone error:', err);
</script>React
React 18 or earlier: add the one-time JSX augmentation from Framework Integration → TypeScript: JSX support for
incode-*tags. React 19+ doesn't need it, and can also use the simpler form from Framework Integration → React 19+ shortcut.
import { useEffect, useRef } from 'react';
import { setup } from '@incodetech/core';
import type { PhoneConfig } from '@incodetech/core/phone';
import '@incodetech/web/phone';
import '@incodetech/web/phone/styles.css';
type PhoneElement = HTMLElement & {
config: PhoneConfig;
onFinish: () => void;
onError: (error: string) => void;
};
await setup({
apiURL: 'https://demo-api.incodesmile.com',
token: 'your-session-token',
});
export function PhoneVerification() {
const ref = useRef<PhoneElement>(null);
useEffect(() => {
const el = ref.current;
if (!el) return;
el.config = {
otpVerification: true,
otpExpirationInMinutes: 5,
prefill: false,
};
el.onFinish = () => console.log('Phone verified!');
el.onError = (err) => console.error('Phone error:', err);
}, []);
return <incode-phone ref={ref} />;
}For Angular (CUSTOM_ELEMENTS_SCHEMA) and Vue (compilerOptions.isCustomElement) setup, see Framework Integration.
Headless Mode
For complete UI control, use the createPhoneManager from @incodetech/core/phone.
Quick Start
import { setup } from '@incodetech/core';
import { createPhoneManager } from '@incodetech/core/phone';
// Initialize SDK first
await setup({
apiURL: 'https://demo-api.incodesmile.com',
token: 'your-session-token',
});
const manager = createPhoneManager({
config: {
otpVerification: true,
otpExpirationInMinutes: 5,
prefill: false,
},
});
manager.subscribe((state) => {
console.log('Status:', state.status);
if (state.status === 'finished') {
console.log('Phone verified!');
manager.stop();
}
});
// Start the flow
manager.load();
// When state is 'inputting', set the phone number and submit
manager.setPhoneNumber('+14155551234', true);
manager.submit();
// When state is 'awaitingOtp', submit the OTP code
manager.submitOtp('ABC123');State Machine Flow
flowchart LR
idle -->|load| inputting
inputting -->|submit| awaitingOtp
awaitingOtp -->|submitOtp| finished
awaitingOtp -.->|back| inputting
awaitingOtp -.->|resendOtp| awaitingOtp
States Reference
| Status | Description | Key Properties |
|---|---|---|
idle | Initial state, waiting for load() | – |
loadingPrefill | Fetching pre-filled phone from backend | – |
inputting | Ready for phone input | countryCode, phonePrefix, prefilledPhone?, phoneError?, otpVerification?, optinEnabled? |
submitting | Submitting phone number to backend | – |
sendingInitialOtp | Sending OTP code via SMS for the first time | – |
resendingOtp | Resending OTP code via SMS | – |
awaitingOtp | Waiting for user to enter OTP | resendTimer, canResend, attemptsRemaining |
verifyingOtp | Verifying OTP code with backend | resendTimer, canResend |
otpError | OTP verification failed | otpError, attemptsRemaining, resendTimer, canResend |
finished | Verification complete | – |
error | Fatal error occurred | error |
State Properties
When status === 'inputting':
| Property | Type | Description |
|---|---|---|
countryCode | string | ISO country code (e.g., 'US', 'MX') |
phonePrefix | string | International dialing prefix (e.g., '+1') |
prefilledPhone | string? | Pre-populated phone number (if prefill: true) |
phoneError | string? | Validation error from server |
When status === 'awaitingOtp':
| Property | Type | Description |
|---|---|---|
resendTimer | number | Seconds remaining before resend allowed |
canResend | boolean | Whether resend button should be enabled |
attemptsRemaining | number | OTP verification attempts left |
When status === 'otpError':
| Property | Type | Description |
|---|---|---|
otpError | string | Error message describing failure |
attemptsRemaining | number | Remaining attempts before lockout |
resendTimer | number | Seconds remaining before resend allowed |
canResend | boolean | Whether resend button should be enabled |
API Methods
| Method | Description | When to Use |
|---|---|---|
load() | Initializes the flow | Always call first |
setPhoneNumber(phone, isValid) | Sets phone number and validation state | When inputting, before submit() |
setOptInGranted(granted) | Sets marketing opt-in preference | When inputting, if opt-in enabled |
submit() | Submits the phone number | After setting valid phone |
setOtpCode(code) | Sets OTP without submitting | When awaitingOtp (controlled input) |
submitOtp(code) | Sets and submits OTP code | When awaitingOtp or otpError |
resendOtp() | Requests new OTP code | When canResend is true |
back() | Returns to phone input | When awaitingOtp |
reset() | Resets to initial state | After finished or error |
stop() | Cleanup resources | When unmounting |
getState() | Returns current state synchronously | Anytime |
subscribe(callback) | Subscribe to state changes | Returns unsubscribe function |
React Example
import { useState, useEffect } from 'react';
import { createPhoneManager, type PhoneState } from '@incodetech/core/phone';
function CustomPhoneVerification() {
const [manager] = useState(() => createPhoneManager({
config: { otpVerification: true, otpExpirationInMinutes: 5, prefill: false },
}));
const [state, setState] = useState<PhoneState>({ status: 'idle' });
const [phone, setPhone] = useState('');
const [otp, setOtp] = useState('');
useEffect(() => {
const unsubscribe = manager.subscribe(setState);
manager.load();
return () => { unsubscribe(); manager.stop(); };
}, [manager]);
const handleSubmitPhone = () => {
// Use a phone validation library like libphonenumber-js
const isValid = phone.length >= 10;
manager.setPhoneNumber(phone, isValid);
manager.submit();
};
switch (state.status) {
case 'inputting':
return (
<div>
<p>Country: {state.countryCode} ({state.phonePrefix})</p>
<input
type="tel"
value={phone}
onChange={(e) => setPhone(e.target.value)}
placeholder="Enter phone number"
/>
{state.phoneError && <p className="error">{state.phoneError}</p>}
<button onClick={handleSubmitPhone}>Send OTP</button>
</div>
);
case 'submitting':
case 'sendingInitialOtp':
case 'resendingOtp':
return <div>Sending OTP...</div>;
case 'awaitingOtp':
return (
<div>
<p>Enter the 6-character code sent to your phone</p>
<input
type="text"
value={otp}
onChange={(e) => setOtp(e.target.value.toUpperCase())}
placeholder="ABC123"
maxLength={6}
/>
<button onClick={() => manager.submitOtp(otp)}>Verify</button>
{state.canResend ? (
<button onClick={() => manager.resendOtp()}>Resend Code</button>
) : (
<p>Resend available in {state.resendTimer}s</p>
)}
<button onClick={() => manager.back()}>Change Phone Number</button>
</div>
);
case 'otpError':
return (
<div>
<p className="error">{state.otpError}</p>
<p>Attempts remaining: {state.attemptsRemaining}</p>
<input
type="text"
value={otp}
onChange={(e) => setOtp(e.target.value.toUpperCase())}
maxLength={6}
/>
<button onClick={() => manager.submitOtp(otp)}>Try Again</button>
</div>
);
case 'verifyingOtp':
return <div>Verifying...</div>;
case 'finished':
return <div>Phone verified successfully!</div>;
case 'error':
return <div className="error">Error: {state.error}</div>;
default:
return <div>Loading...</div>;
}
}Configuration Options
PhoneConfig shape:
| Option | Type | Required | Description |
|---|---|---|---|
otpVerification | boolean | ✅ | Require OTP (SMS code) verification. If false, the phone is verified immediately after submission. |
otpExpirationInMinutes | number | ✅ | How long the OTP code remains valid. After expiration the user must request a new code. |
prefill | boolean | ✅ | Pre-populate with the user's previously stored phone number. Useful for returning users. |
isInstantVerify | boolean | ❌ | Use carrier-based instant verification instead of OTP. Not available in all regions. Default false. |
optinEnabled | boolean | ❌ | Show a marketing opt-in checkbox. The user's preference is sent with the phone submission. Default false. |
maxOtpAttempts | number | ❌ | Maximum OTP verification attempts before lockout. Default 3. |
Troubleshooting
OTP Not Received
- Verify the phone number is correct (include country code)
- Check the phone can receive SMS
- Some VoIP numbers may not receive SMS
- Check with your carrier if SMS is blocked
Invalid OTP Error
- OTPs expire after the configured time (default 5 minutes)
- Each OTP can only be used once
- Request a new OTP if the current one expires
- Check
attemptsRemainingbefore lockout
Phone Number Rejected
- Ensure the number includes country code
- Some numbers may be blacklisted
- Corporate or VoIP numbers may not be accepted
See Also
- Headless Mode: Complete headless API reference
- Individual Modules: Overview of all modules
Updated 1 day ago
