#SMS One-Time Code (SMS OTP)

6 min read

SMS one-time code is an optional second factor for portal sign-in. The role policy permits it for client_admin and partner_admin accounts only, and it is only available when your organisation has been opted in by an AiDial operator. The feature is deny-by-default unless the current tenant's SMS OTP override is enabled. That override is the mfa.sms_otp.enabled flag, read from the mfa_features.sms_otp.enabled field on the session-context response and propagated through the portal session; it defaults to OFF.

In the current portal UI, the self-service SMS enrolment card is rendered only on the client_admin Settings → Profile & Security surface. partner_admin accounts are policy-allowed at the enrolment route but see a read-only, provider-managed security block instead of the SMS card (see Who can use it).

#What it is

When SMS OTP is enrolled, Zitadel sends a one-time code by text message during sign-in. You enter the code on the Zitadel sign-in screen to complete the second factor. AiDial Portal does not collect or validate SMS code values itself.

#How enrolment works

When the portal renders the SMS factor card on the client_admin Settings → Profile & Security surface, it appears only if SMS one-time code is available for your role and organisation (the card is hidden entirely unless your resolved allowed_factors includes sms_otp, which folds the role and tenant-override checks together). Before enrolment the card shows the privacy notice and an Australian mobile-number field.

The browser submits the phone number to the portal BFF route POST /api/auth/mfa-sms-enrol. The route enforces CSRF and session authentication, checks the role policy (client_admin / partner_admin only) and the tenant override, validates the number, applies rate limits, and then calls Zitadel's SMS factor endpoint (POST /management/v1/users/{user_id}/auth_factors/otp_sms) server-side. The browser does not send an X-API-Key.

On success, the response contains only a masked phone_tail such as +61 ... 678.

#Who can use it

RoleEligibility
client_adminYes (when the organisation override is enabled) — self-service enrolment card shown on Settings → Profile & Security
partner_adminPolicy-allowed at the enrolment route (when the organisation override is enabled), but the current portal shows a read-only, provider-managed security block instead of the SMS enrolment card
client_manager, client_staff, partner_userNot available — use authenticator app (TOTP)
aidial_admin, aidial_operatorNot available (hard-denied) — use authenticator app (TOTP)

#Privacy Notice (APP-5)

By enrolling SMS one-time code, you consent to AiDial Pty Ltd disclosing your mobile number to Twilio Inc. (United States) — our authentication SMS sub-processor — for the sole purpose of delivering security codes. Twilio handles this data outside Australia. You may withdraw consent by removing SMS one-time code from your account in Settings (the portal launches the identity-provider management flow to complete removal when available).

This is the same wording shown in the in-app privacy notice on the enrolment card.

#What we store

SurfaceWhat we hold
Portal session/JWTMFA factor identifiers and state only; no full phone number and no persisted phone tail
SMS enrolment responseMasked phone tail (+## ... 999) only
Portal databaseNo SMS OTP phone-number table or SMS factor copy; audit events only
Portal audit logFactor, outcome, request metadata, and masked phone tail when available
Browser local storageNothing; the masked tail is held only in the current page state after enrolment
Zitadel identity storeFull phone number as the SMS authentication factor
TwilioPhone number + delivery status only

The portal briefly receives the phone number once at enrolment time so it can validate the country prefix, derive SHA-256 hashes for rate-limit buckets, and forward the number to Zitadel. The plaintext is discarded after the Zitadel call. Zitadel holds the canonical SMS factor record; the portal's later enrolled-factor lookup reads only factor presence, not the full phone number or phone tail.

SMS enrolment audit metadata is passed through the MFA audit redactor. Full phone-number keys, raw OTP values, recovery codes, session tokens, cookies, and bearer headers are stripped before audit emission.

#Supported phone numbers

  • Australia (+61) only.
  • Non-Australian E.164 numbers are rejected before the portal calls Zitadel.

#Delivery failures and carrier opt-out

If SMS delivery is blocked, the enrolment flow can surface a delivery/provider warning. If you previously replied STOP, the carrier may block future SMS delivery. Use a different second factor, such as an authenticator app or recovery code, or update/remove the SMS factor through the provider management flow when available.

#What we do not do

  • We do NOT send marketing or promotional SMS.
  • We do NOT send appointment reminders, booking confirmations, or other business-flow SMS through this surface. Those use a separate Twilio configuration in the AiDial voice runtime.
  • We do NOT use SMS OTP for any AiDial voice or chat runtime.
  • We do NOT share your phone number with any third party other than the identity-provider/SMS delivery path described above.

#Removing SMS OTP

Use the MFA management action in Settings Profile & Security or Account > Security when a trusted provider management or disable action is available. The portal opens the Zitadel management page in a new tab. The portal does not directly delete the SMS factor; complete removal in Zitadel, then return to the portal and refresh your security status.

#Troubleshooting

  • "SMS one-time code is not available for this organisation": your organisation has not opted in. Contact your administrator.
  • "SMS one-time code is not available for this account type": your role does not allow SMS OTP. Use authenticator app (TOTP) or recovery code.
  • "Only Australian phone numbers are supported": AiDial supports +61 numbers only at launch.
  • "Too many SMS one-time code requests": the rate limit has been reached. The portal enforces per-phone and per-user buckets (3 per minute and 10 per day), a trusted source-IP bucket when proxy headers are verified (30 per hour), and a global bucket (100 per minute). Wait one minute and try again.
  • "We could not deliver the SMS to this number": the provider was unavailable or the carrier may have blocked delivery. Update your number through the provider management flow when available, or use a different second factor.
  • "SMS OTP provider is not configured": the identity-provider SMS integration is unavailable. Use another second factor and contact support.

#Operator runtime dependencies

This flow depends on the portal's standard required runtime configuration: NEXTAUTH_SECRET, NEXTAUTH_URL, AIDIAL_API_BASE_URL, AIDIAL_API_TIMEOUT_MS, ZITADEL_ISSUER, ZITADEL_CLIENT_ID, and ZITADEL_CLIENT_SECRET. These values are required; the portal code does not define fallback defaults for them.

SMS enrolment also requires the identity-provider SMS factor creation integration to be configured. The portal's Zitadel SMS factor client reads the operator admin token ZITADEL_MFA_ADMIN_PAT and the existing ZITADEL_ISSUER; when either is missing the call fails closed with provider_not_configured, and POST /api/auth/mfa-sms-enrol returns 503 with mfa_sms_provider_unavailable. A Zitadel 5xx, timeout, or transport failure also maps to 503 / mfa_sms_provider_unavailable; a Zitadel 4xx rejection of the number maps to 400 / phone_invalid.

#Sub-processor disclosure

Sub-processorCountryPurposeData shared
Twilio Inc.United StatesDeliver authentication SMSMobile number + delivery status
(See multi-factor-auth.md for the email OTP sub-processor disclosure)