Phone Module
This guide is specific to Web SDK 2.0. If you are still using 1.x, you can find documentation here. Contact your Incode Representative for upgrade information and check if you are a candidate for this upgrade.
Full rollout to all clients still TBD.
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
