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):

PropertyTypeRequiredDescription
configPhoneConfigConfiguration options
onFinish() => voidCalled when verification completes
onError(error: string) => voidCalled 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

StatusDescriptionKey Properties
idleInitial state, waiting for load()
loadingPrefillFetching pre-filled phone from backend
inputtingReady for phone inputcountryCode, phonePrefix, prefilledPhone?, phoneError?, otpVerification?, optinEnabled?
submittingSubmitting phone number to backend
sendingInitialOtpSending OTP code via SMS for the first time
resendingOtpResending OTP code via SMS
awaitingOtpWaiting for user to enter OTPresendTimer, canResend, attemptsRemaining
verifyingOtpVerifying OTP code with backendresendTimer, canResend
otpErrorOTP verification failedotpError, attemptsRemaining, resendTimer, canResend
finishedVerification complete
errorFatal error occurrederror

State Properties

When status === 'inputting':

PropertyTypeDescription
countryCodestringISO country code (e.g., 'US', 'MX')
phonePrefixstringInternational dialing prefix (e.g., '+1')
prefilledPhonestring?Pre-populated phone number (if prefill: true)
phoneErrorstring?Validation error from server

When status === 'awaitingOtp':

PropertyTypeDescription
resendTimernumberSeconds remaining before resend allowed
canResendbooleanWhether resend button should be enabled
attemptsRemainingnumberOTP verification attempts left

When status === 'otpError':

PropertyTypeDescription
otpErrorstringError message describing failure
attemptsRemainingnumberRemaining attempts before lockout
resendTimernumberSeconds remaining before resend allowed
canResendbooleanWhether resend button should be enabled

API Methods

MethodDescriptionWhen to Use
load()Initializes the flowAlways call first
setPhoneNumber(phone, isValid)Sets phone number and validation stateWhen inputting, before submit()
setOptInGranted(granted)Sets marketing opt-in preferenceWhen inputting, if opt-in enabled
submit()Submits the phone numberAfter setting valid phone
setOtpCode(code)Sets OTP without submittingWhen awaitingOtp (controlled input)
submitOtp(code)Sets and submits OTP codeWhen awaitingOtp or otpError
resendOtp()Requests new OTP codeWhen canResend is true
back()Returns to phone inputWhen awaitingOtp
reset()Resets to initial stateAfter finished or error
stop()Cleanup resourcesWhen unmounting
getState()Returns current state synchronouslyAnytime
subscribe(callback)Subscribe to state changesReturns 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:

OptionTypeRequiredDescription
otpVerificationbooleanRequire OTP (SMS code) verification. If false, the phone is verified immediately after submission.
otpExpirationInMinutesnumberHow long the OTP code remains valid. After expiration the user must request a new code.
prefillbooleanPre-populate with the user's previously stored phone number. Useful for returning users.
isInstantVerifybooleanUse carrier-based instant verification instead of OTP. Not available in all regions. Default false.
optinEnabledbooleanShow a marketing opt-in checkbox. The user's preference is sent with the phone submission. Default false.
maxOtpAttemptsnumberMaximum 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