Phone Module
Web SDK 2.0 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).
Tag
<incode-phone> is a standard Web Component. Importing the UI subpath registers the custom element; importing the CSS applies the module's styles.
Code Snippet
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
Code Snippet
<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
Code Snippet
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} />;
}
Headless Mode
For complete UI control, use the createPhoneManager from @incodetech/core/phone.
Quick Start
Code Snippet
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
load
submit
submitOtp
back
resendOtp
idle
inputting
awaitingOtp
finished
States Reference
| Status | Description | Key Properties |
|---|---|---|
idle |
Initial state, waiting for load() |
– |
loadingPrefill |
Fetching pre-filled phone from backend | – |
loadingStartInfo |
Fetching start info (ipIsoCode, phonePrefix) before the field is interactive |
prefilledPhone?, otpVerification?, optinEnabled? |
inputting |
Ready for phone input | countryCode, phonePrefix, prefilledPhone?, phoneError?, validationErrors?, isValid, otpVerification?, optinEnabled? |
submitting |
Submitting phone number to backend | – |
sendingInitialOtp |
Sending OTP code via SMS for the first time | – |
resendingOtp |
Resending OTP code via SMS | otpCode, otpLength |
awaitingOtp |
Waiting for user to enter OTP | resendTimer, canResend, attemptsRemaining, otpCode, otpLength, otpValidationErrors?, isOtpValid |
verifyingOtp |
Verifying OTP code with backend | resendTimer, canResend, otpCode, otpLength |
otpError |
OTP verification failed | otpError, attemptsRemaining, resendTimer, canResend, otpCode, otpLength, otpValidationErrors?, isOtpValid |
finished |
Verification complete | – |
error |
Fatal error occurred | error |
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() |
submit() |
Submits the phone number | After setting valid phone |
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
Code Snippet
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 = () => {
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 '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>
)}
</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 '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 blocked
- Corporate or VoIP numbers may not be accepted