SMS Business

Build a SaaS Product with OTP Auth

Technical guide for SaaS founders on implementing OTP authentication. Covers architecture, UX design, cost projections, scaling, and integration with StartMessaging API.

10 February 202614 min read

StartMessaging Team

Engineering

When you are building a SaaS product targeting Indian users, one of the first decisions you face is how users will authenticate. Passwords have been the default for decades, but OTP-based authentication has become the standard for Indian consumer apps. Services from Swiggy to PhonePe to Aadhaar verification all use OTP as a primary authentication method.

This guide is for SaaS founders and technical leads who are evaluating OTP as their primary or secondary authentication method. We cover the architecture decisions, user experience patterns, cost implications, and practical implementation details you need to make an informed choice.

Why OTP for SaaS Authentication

OTP-based authentication offers several advantages that are particularly relevant for Indian SaaS products:

  • No password management overhead: No password hashing, no reset flows, no breach notifications when a password database is compromised. The OTP is generated, delivered, verified, and discarded. There is nothing to store or protect long-term.
  • Higher conversion rates: Registration requires only a phone number. No email verification, no password creation with complexity requirements, no CAPTCHA. Every additional form field you remove increases your sign-up conversion rate.
  • Verified phone number from day one: You know the user controls the phone number they provided. This is valuable for communication (transactional SMS, support), fraud prevention, and compliance requirements like KYC.
  • Familiarity for Indian users: OTP login is the dominant pattern in India. Users expect it and are comfortable with the flow. Choosing passwords over OTP would actually feel unusual to most Indian consumers.
  • Reduced support burden: Password reset is consistently one of the top support tickets for SaaS products. OTP auth eliminates this category entirely.

OTP vs Password: The Trade-offs

OTP is not strictly superior to passwords in every situation. Here is an honest comparison:

FactorOTP AuthPassword Auth
Sign-up frictionLow (phone number only)Medium (email + password + verification)
Login speedDepends on SMS delivery (5-30 seconds)Instant (type password)
Offline loginNot possiblePossible with cached credentials
Per-login costRs 0.25 per OTPZero marginal cost
Security surfaceSIM swap, SS7 attacks (rare)Credential stuffing, phishing, breaches
Support burdenLow (no password resets)High (password resets dominate)
User familiarity (India)Very highHigh

The most significant trade-off is cost. Password auth has zero marginal cost per login, while OTP auth costs Rs 0.25 per login with StartMessaging. For a SaaS product with 10,000 daily logins, that is Rs 2,500 per day or about Rs 75,000 per month. Whether this cost is acceptable depends on your product’s revenue per user and login frequency.

Architecture Decisions

Before writing any code, make these architectural decisions:

OTP as Primary vs Secondary Auth

Primary: OTP is the only way to log in. No passwords exist. Best for consumer apps, delivery platforms, and services where simplicity and phone verification are paramount.

Secondary: Users have passwords, and OTP is used for two-factor authentication, high-risk actions (payment, profile changes), or as a password-reset mechanism. Best for B2B SaaS, fintech, and products with compliance requirements.

Session Strategy

OTP-based systems need thoughtful session management because every re-authentication costs money:

  • Long-lived sessions: Issue JWT tokens with longer expiry (7 to 30 days) and use refresh tokens. This reduces re-login frequency and OTP costs but requires robust token revocation.
  • Short sessions with remember-me: Default to short sessions (24 hours) but offer a "remember this device" option that extends the session. Users who opt in avoid repeated OTP prompts.
  • Device fingerprinting: Trust recognized devices for longer sessions and only require OTP for new or unrecognized devices. This significantly reduces OTP volume for returning users.

Provider Abstraction

Do not hardcode a single OTP provider into your application. Build an abstraction layer:

// OTP provider interface
interface OtpProvider {
  sendOtp(phoneNumber: string): Promise<{ requestId: string }>;
  verifyOtp(requestId: string, code: string): Promise<{ verified: boolean }>;
}

// StartMessaging implementation
class StartMessagingProvider implements OtpProvider {
  async sendOtp(phoneNumber: string) {
    const response = await fetch('https://api.startmessaging.com/otp/send', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-API-Key': process.env.STARTMESSAGING_API_KEY,
      },
      body: JSON.stringify({ phoneNumber }),
    });
    const { data } = await response.json();
    return { requestId: data.requestId };
  }

  async verifyOtp(requestId: string, code: string) {
    const response = await fetch('https://api.startmessaging.com/otp/verify', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-API-Key': process.env.STARTMESSAGING_API_KEY,
      },
      body: JSON.stringify({ requestId, otpCode: code }),
    });
    const { data } = await response.json();
    return { verified: data.verified };
  }
}

This pattern makes it straightforward to add fallback providers, run A/B tests on delivery, or migrate providers without touching your core auth logic.

User Experience Design for OTP Flows

The UX of your OTP flow directly impacts conversion, support volume, and user satisfaction. Here are the patterns that work:

Registration Flow

  1. User enters phone number on a clean, single-input screen.
  2. Show a loading indicator while the OTP is being sent (typically 2 to 5 seconds).
  3. Redirect to a 4 to 6-digit code input screen. Auto-focus the first input field.
  4. Support auto-read on Android (using the SMS Retriever API) so the code is filled automatically.
  5. Show a countdown timer (60 seconds) before allowing a resend.
  6. After successful verification, go directly to onboarding. Do not ask for a password.

Login Flow

  1. User enters their registered phone number.
  2. Send OTP immediately (do not confirm before sending).
  3. Code entry screen with auto-read support.
  4. On success, issue a session token and redirect to the dashboard.

Key UX Principles

  • Pre-fill the country code: Default to +91 for Indian apps. Do not make users type it.
  • Format the phone number visually: Display as +91 98765 43210 for readability while storing as +919876543210 internally.
  • Show the masked number on the OTP screen: "OTP sent to +91 ****3210" confirms the user did not mistype.
  • Allow editing the phone number: If the user made a typo, let them go back and correct it without starting over.
  • Handle errors gracefully: Wrong code? Show how many attempts remain. Expired? Offer a resend button. Failed delivery? Suggest checking the number.

Implementation with StartMessaging

Here is a practical implementation outline using the StartMessaging OTP API:

Backend: Auth Service

// auth.service.ts
class AuthService {
  async initiateLogin(phoneNumber: string) {
    // 1. Send OTP via StartMessaging
    const otpResult = await this.otpProvider.sendOtp(phoneNumber);

    // 2. Create or find user record
    let user = await this.userRepo.findByPhone(phoneNumber);
    if (!user) {
      user = await this.userRepo.create({ phoneNumber });
    }

    // 3. Store requestId for verification
    await this.sessionStore.set(
      `otp:${phoneNumber}`,
      otpResult.requestId,
      { ttl: 600 } // 10 minutes
    );

    return { message: 'OTP sent', expiresInSeconds: 600 };
  }

  async verifyLogin(phoneNumber: string, code: string) {
    // 1. Retrieve stored requestId
    const requestId = await this.sessionStore.get(`otp:${phoneNumber}`);
    if (!requestId) throw new Error('OTP expired or not found');

    // 2. Verify via StartMessaging
    const result = await this.otpProvider.verifyOtp(requestId, code);
    if (!result.verified) throw new Error('Invalid OTP');

    // 3. Issue session token
    const user = await this.userRepo.findByPhone(phoneNumber);
    const token = this.jwt.sign({ userId: user.id, phone: phoneNumber });

    // 4. Clean up
    await this.sessionStore.delete(`otp:${phoneNumber}`);

    return { token, user };
  }
}

Frontend: React Login Component

// Simplified React OTP login flow
function LoginPage() {
  const [step, setStep] = useState('phone'); // 'phone' | 'otp'
  const [phone, setPhone] = useState('');

  async function handleSendOtp() {
    await apiPost('/auth/send-otp', { phoneNumber: `+91${phone}` });
    setStep('otp');
  }

  async function handleVerify(code: string) {
    const { token } = await apiPost('/auth/verify-otp', {
      phoneNumber: `+91${phone}`,
      otpCode: code,
    });
    localStorage.setItem('token', token);
    window.location.href = '/dashboard';
  }

  return step === 'phone'
    ? <PhoneInput value={phone} onChange={setPhone} onSubmit={handleSendOtp} />
    : <OtpInput phone={phone} onVerify={handleVerify} onBack={() => setStep('phone')} />;
}

Cost Projections for SaaS Founders

Understanding your OTP costs at different growth stages helps with financial planning. Here is a projection based on Rs 0.25 per OTP with StartMessaging:

Growth StageMonthly Active UsersLogins/Month*OTP Cost/Month
Pre-launch100400Rs 100
Early Traction1,0004,000Rs 1,000
Product-Market Fit10,00040,000Rs 10,000
Growth50,000150,000Rs 37,500
Scale200,000400,000Rs 1,00,000

*Assumes an average of 3 to 4 logins per user per month. Actual frequency depends on your product. Note that device trust and long-lived sessions significantly reduce the OTP-to-login ratio.

Compare these numbers against the cost of password-related support tickets (typically $5 to $15 per ticket in support staff time) and the conversion improvement from simplified registration. For most SaaS products, the OTP cost is offset by reduced support expenses and higher conversion.

Security Considerations

OTP auth has its own security profile. Address these threats:

  • OTP bombing / enumeration: Rate-limit OTP requests per phone number (maximum 3 per hour) and per IP address. This prevents abuse and protects your wallet balance.
  • Brute force: The StartMessaging API limits verification attempts per OTP request (default 3 attempts). After exhaustion, a new OTP must be requested.
  • SIM swap attacks: For high-value accounts (admin users, financial actions), consider adding a second factor like email confirmation or authenticator apps.
  • Session fixation: Generate a new session token on every successful OTP verification. Never reuse session identifiers.
  • Phone number recycling: Telecom operators recycle inactive numbers. If a user has not logged in for 12+ months, consider requiring re-verification of identity when they return.

Scaling OTP Auth

As your SaaS product grows, OTP authentication introduces specific scaling challenges:

  • Reduce OTP frequency: Use device trust tokens to skip OTP for recognized devices. This can reduce your OTP volume by 60% to 80% as most logins come from returning users on the same device.
  • Implement session refresh: Instead of expiring sessions and forcing a new OTP, use refresh tokens that extend the session seamlessly. Reserve OTP for new device logins.
  • Monitor delivery rates: Track OTP delivery success rates by carrier. If delivery to a specific carrier degrades, you need to know immediately. StartMessaging uses priority-based provider fallback to maintain high delivery rates.
  • Wallet balance automation: Set up automated wallet top-ups or alerts when your balance drops below a threshold. At scale, a depleted wallet means no user can log in.
  • Graceful degradation: If SMS delivery fails, offer a fallback (email OTP, voice call, retry after 30 seconds). Do not leave users locked out.

Hybrid Authentication Approaches

Many successful Indian SaaS products use a hybrid model:

  • OTP for registration, password for login: Users verify their phone number via OTP during sign-up, then set a password for future logins. This gives you a verified phone number and zero-cost logins.
  • Password primary, OTP for 2FA: Standard password login with OTP as the second factor for sensitive operations. This is common in fintech and B2B SaaS.
  • OTP for mobile, password for web: Optimize for the platform. Mobile users expect OTP (with auto-read). Desktop users may prefer passwords.
  • Magic link plus OTP: Email a magic link as the primary login method and use OTP for phone verification and sensitive actions. This balances cost and security.

The right combination depends on your user base, security requirements, and cost sensitivity. Start with the simplest approach that meets your requirements and add complexity only when needed.

FAQ

Ready to build? Check the StartMessaging API documentation for endpoint details, or read our OTP verification flow guide for deeper implementation patterns. Start with Rs 0.25 per OTP and scale your auth as your product grows.

Ready to Send OTPs?

Integrate StartMessaging in 5 minutes. No DLT registration required.