SDK reference · Incode Web SDK 2 Reference / Web SDK 2 Individual Modules

Phone Module

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 attemptsRemaining before 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

Was this page helpful?