TOTP Hosted Integration

Implement TOTP authentication using Akedly's hosted pages for the fastest deployment with zero UI development. This approach provides a complete white-labeled experience with your branding while handling all complex UI states, error handling, and cross-platform compatibility automatically.

Overview

With hosted integration, Akedly provides complete TOTP setup and authentication pages that match your company branding. Users are redirected to these pages when they need to set up TOTP or authenticate, then returned to your application with the results.

How It Works:

  1. Create a TOTP Pipeline in your dashboard with callback URLs
  2. Redirect users to Akedly's hosted setup page: https://app.akedly.io/totp/setup
  3. Users complete TOTP setup on the hosted page with your branding
  4. Users are redirected back to your application
  5. For authentication, redirect to hosted auth page: https://app.akedly.io/totp/request
  6. Receive webhooks and frontend callbacks with results

Key Benefits:

  • Zero Frontend Development: No UI components to build
  • Cross-Platform Support: Works on web, mobile, and desktop automatically
  • Error Handling: All edge cases handled (expired requests, rate limiting, etc.)
  • White-Label Branding: Uses your company name, logo, and colors
  • Mobile Optimized: Automatic mobile/desktop UI adaptation
  • Webhook Integration: Real-time server notifications

Prerequisites

Before starting, ensure you have completed the basic Akedly setup:

  1. Akedly Account: Sign up if you haven't already
  2. API Key: Available in your dashboard under "View API Key"
  3. Company Profile: Complete your company information in the dashboard for branding
  4. Test Environment: Start with test mode before going live

Company Profile Setup

Step 1: Create TOTP Pipeline

A TOTP Pipeline configures how hosted pages integrate with your application through callback URLs and redirects.

Create Pipeline in Dashboard

Navigate to TOTP Pipelines in your Akedly dashboard and create a new pipeline:

TOTP Pipeline Creation

Pipeline Configuration:

  • Name
    Pipeline Name
    Type
    string
    Description

    Descriptive name for internal reference (e.g., "Production Mobile App TOTP")

  • Name
    Description
    Type
    string
    Description

    Optional description for team documentation

  • Name
    Frontend Callback URL
    Type
    string
    Description

    Where users are redirected after completing setup or authentication on web browsers

  • Name
    Backend Callback URL
    Type
    string
    Description

    Webhook endpoint for server-to-server notifications (optional but recommended)

  • Name
    Mobile Redirect URL
    Type
    string
    Description

    Deep link for mobile app returns (e.g., "myapp://totp-complete")

  • Name
    Custom Scheme
    Type
    string
    Description

    Your mobile app's URL scheme (e.g., "myapp")

  • Name
    Type
    Type
    string
    Description

    "Test" for development, "Live" for production

Example Pipeline Configuration

Pipeline Setup Examples

{
  "name": "Web App TOTP Authentication",
  "description": "TOTP integration for main web application",
  "frontendCallbackURL": "https://myapp.com/auth/totp-complete",
  "backendCallbackURL": "https://api.myapp.com/webhooks/totp",
  "type": "Live"
}

TOTP Pipeline Configuration

Step 2: Implement User Setup Flow

When a user needs to set up TOTP authentication, redirect them to Akedly's hosted setup page. The hosted page handles QR code generation, deep links, manual setup options, and device detection automatically.

Setup URL Structure

https://app.akedly.io/totp/setup?pipeline={pipelineID}&identifier={userIdentifier}

Required Parameters:

  • pipeline: The TOTP Pipeline ID from Step 1
  • identifier: Unique user identifier (email, username, or user ID)

Implementation Examples

Setup Flow Implementation

// Store your pipeline ID (from dashboard)
const TOTP_PIPELINE_ID = 'your_pipeline_id_here'

function setupTOTPForUser(userEmail) {
  // Construct hosted setup URL
  const setupURL = `https://app.akedly.io/totp/setup?pipeline=${TOTP_PIPELINE_ID}&identifier=${encodeURIComponent(userEmail)}`

  // Redirect user to hosted setup page
  window.location.href = setupURL

  // User will complete setup on Akedly's hosted page
  // They'll be redirected back to your frontendCallbackURL when done
}

// Example usage
document.getElementById('setup-totp-btn').addEventListener('click', () => {
  const userEmail = getCurrentUserEmail() // Your function to get user email
  setupTOTPForUser(userEmail)
})

What Happens During Setup

  1. User Redirection: User is redirected to https://app.akedly.io/totp/setup?pipeline=ID&identifier=email
  2. Hosted Page Loading: Akedly loads the setup page with your company branding
  3. TOTP User Creation: System automatically creates TOTP configuration for the user
  4. Setup Options Display: Page shows QR code, deep link, or manual setup based on device
  5. User Setup: User adds account to their authenticator app
  6. Verification: User enters first TOTP code to verify setup
  7. Completion: User is redirected back to your application

TOTP Hosted Setup Page

Step 3: Implement Authentication Flow

When you need to authenticate a user with TOTP (e.g., at login, for sensitive operations), create an authentication request and redirect them to the hosted authentication page.

Authentication Flow Steps

Authentication Implementation

// Complete authentication flow for web applications

async function authenticateUserWithTOTP(userEmail) {
  try {
    // Step 1: Create authentication request
    const response = await fetch(
      'https://api.akedly.io/api/v1/totp/request-auth',
      {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          APIKey: 'your_api_key_here',
          identifier: userEmail,
          pipelineID: 'your_pipeline_id_here',
        }),
      },
    )

    const data = await response.json()

    if (data.status === 'success') {
      const authRequestID = data.data.authRequestID
      const expiresAt = data.data.expiresAt

      // Step 2: Redirect to hosted authentication page
      const authURL = `https://app.akedly.io/totp/request?requestID=${authRequestID}`

      // Store request ID for potential cleanup
      sessionStorage.setItem('totp_auth_request', authRequestID)
      sessionStorage.setItem('totp_auth_expires', expiresAt)

      // Redirect user to hosted auth page
      window.location.href = authURL

      // User will enter TOTP code on hosted page
      // They'll be redirected back with results
    } else {
      // Handle errors (user not found, quota exceeded, etc.)
      showError(data.message)
    }
  } catch (error) {
    console.error('Authentication request failed:', error)
    showError('Failed to start authentication. Please try again.')
  }
}

// Helper function to check if user has TOTP enabled
async function checkTOTPStatus(userEmail) {
  try {
    // This would be your own API endpoint to check TOTP status
    const response = await fetch(
      `/api/users/${encodeURIComponent(userEmail)}/totp-status`,
    )
    const data = await response.json()
    return data.totpEnabled
  } catch (error) {
    console.error('Failed to check TOTP status:', error)
    return false
  }
}

// Example login flow integration
async function handleLogin(email, password) {
  // Step 1: Verify email/password
  const loginResponse = await fetch('/api/login', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ email, password }),
  })

  if (loginResponse.ok) {
    // Step 2: Check if user has TOTP enabled
    const hasTOTP = await checkTOTPStatus(email)

    if (hasTOTP) {
      // Step 3: Require TOTP authentication
      await authenticateUserWithTOTP(email)
      // User will be redirected to hosted TOTP page
    } else {
      // User doesn't have TOTP, complete login
      completeLogin()
    }
  } else {
    showError('Invalid email or password')
  }
}

What Happens During Authentication

  1. Auth Request Creation: Your backend calls the TOTP request API to create an authentication request
  2. User Redirection: User is redirected to https://app.akedly.io/totp/request?requestID=ID
  3. Hosted Auth Page: Akedly displays TOTP input form with your branding
  4. Code Entry: User enters 6-digit code from their authenticator app
  5. Verification: System verifies the code and processes billing
  6. Completion: User is redirected back to your application with results

TOTP Hosted Authentication Page

Step 4: Handle Callbacks

After users complete setup or authentication on hosted pages, they're redirected back to your application. You'll also receive webhook notifications for server-side processing.

Frontend Callback Handling

Users are redirected to your frontendCallbackURL with query parameters containing the results.

Callback Handling

// Handle callbacks when users return from hosted pages

function handleTOTPCallback() {
  const urlParams = new URLSearchParams(window.location.search)

  // Common parameters for both setup and authentication
  const status = urlParams.get('status')
  const identifier = urlParams.get('identifier')
  const timestamp = urlParams.get('timestamp')

  if (status === 'success') {
    // Check if this was setup or authentication
    const totpID = urlParams.get('totpID')
    const requestID = urlParams.get('requestID')

    if (totpID) {
      // This was a setup completion
      handleSetupSuccess(identifier, totpID)
    } else if (requestID) {
      // This was an authentication completion
      handleAuthSuccess(identifier, requestID)
    }
  } else if (status === 'cancelled') {
    const reason = urlParams.get('reason')
    handleCancellation(reason)
  } else if (status === 'expired') {
    handleExpiration()
  } else if (status === 'error') {
    const errorReason = urlParams.get('reason')
    handleError(errorReason)
  }

  // Clean up URL parameters
  if (urlParams.has('status')) {
    const cleanURL = window.location.pathname
    window.history.replaceState({}, document.title, cleanURL)
  }
}

function handleSetupSuccess(identifier, totpID) {
  console.log(`TOTP setup successful for ${identifier}, ID: ${totpID}`)

  // Update UI to show TOTP is now enabled
  document.getElementById('totp-status').textContent =
    'Two-Factor Authentication: Enabled'
  document.getElementById('setup-totp-btn').style.display = 'none'
  document.getElementById('disable-totp-btn').style.display = 'block'

  // Show success message
  showSuccessMessage('Two-factor authentication has been set up successfully!')

  // Update user's TOTP status in your system
  updateUserTOTPStatus(identifier, true)
}

function handleAuthSuccess(identifier, requestID) {
  console.log(
    `TOTP authentication successful for ${identifier}, Request: ${requestID}`,
  )

  // Clear any stored auth request data
  sessionStorage.removeItem('totp_auth_request')
  sessionStorage.removeItem('totp_user_email')

  // Complete login or sensitive operation
  completeAuthentication(identifier)

  // Show success message
  showSuccessMessage('Authentication successful!')

  // Redirect to intended destination
  const intendedURL = sessionStorage.getItem('intended_url') || '/dashboard'
  sessionStorage.removeItem('intended_url')
  window.location.href = intendedURL
}

function handleCancellation(reason) {
  console.log('User cancelled TOTP process:', reason)
  showInfoMessage('Authentication was cancelled. You can try again anytime.')
}

function handleExpiration() {
  console.log('TOTP request expired')
  showWarningMessage('The authentication request expired. Please try again.')
}

function handleError(reason) {
  console.error('TOTP process failed:', reason)
  showErrorMessage(
    'Authentication failed. Please try again or contact support.',
  )
}

// Call callback handler when page loads
document.addEventListener('DOMContentLoaded', handleTOTPCallback)

Webhook Handling

Set up webhook endpoints to receive server-to-server notifications about TOTP events.

Webhook Implementation

const express = require('express')
const app = express()

// Middleware to parse JSON payloads
app.use(express.json())

// TOTP webhook endpoint
app.post('/api/webhooks/totp', (req, res) => {
  try {
    const {
      status,
      verified,
      requestID,
      identifier,
      billingAmount,
      pipelineID,
      totpRequestStatus,
      totpRequestID,
      totpID,
      timestamp,
    } = req.body

    console.log('TOTP webhook received:', {
      status,
      identifier,
      requestID: requestID || totpRequestID,
      verified,
      billingAmount,
    })

    if (status === 'success') {
      if (totpID) {
        // TOTP setup completed
        handleTOTPSetupComplete(identifier, totpID)
      } else if (verified) {
        // TOTP authentication successful
        handleTOTPAuthSuccess(
          identifier,
          requestID || totpRequestID,
          billingAmount,
        )
      }
    } else {
      // Handle failures
      handleTOTPFailure(identifier, status, requestID || totpRequestID)
    }

    // Always respond with 200 to acknowledge receipt
    res.status(200).json({ received: true })
  } catch (error) {
    console.error('Webhook processing error:', error)
    res.status(500).json({ error: 'Internal server error' })
  }
})

async function handleTOTPSetupComplete(identifier, totpID) {
  try {
    // Update user in database
    await db.users.update(
      { email: identifier },
      {
        totp_enabled: true,
        totp_id: totpID,
        updated_at: new Date(),
      },
    )

    // Send confirmation email
    await sendEmail(
      identifier,
      'TOTP Setup Complete',
      'Two-factor authentication has been successfully enabled for your account.',
    )

    // Log for analytics
    analytics.track('totp_setup_completed', {
      user_email: identifier,
      totp_id: totpID,
      timestamp: new Date(),
    })

    console.log(`TOTP setup completed for ${identifier}`)
  } catch (error) {
    console.error(`Failed to process TOTP setup for ${identifier}:`, error)
  }
}

async function handleTOTPAuthSuccess(identifier, requestID, billingAmount) {
  try {
    // Update authentication log
    await db.auth_logs.create({
      user_email: identifier,
      auth_type: 'totp',
      request_id: requestID,
      status: 'success',
      billing_amount: billingAmount,
      timestamp: new Date(),
    })

    // Update user's last authentication time
    await db.users.update({ email: identifier }, { last_totp_auth: new Date() })

    // Log for analytics
    analytics.track('totp_auth_success', {
      user_email: identifier,
      request_id: requestID,
      billing_amount: billingAmount,
      timestamp: new Date(),
    })

    console.log(
      `TOTP authentication successful for ${identifier}, charged ${billingAmount} EGP`,
    )
  } catch (error) {
    console.error(
      `Failed to process TOTP auth success for ${identifier}:`,
      error,
    )
  }
}

async function handleTOTPFailure(identifier, status, requestID) {
  try {
    // Log failed attempt
    await db.auth_logs.create({
      user_email: identifier,
      auth_type: 'totp',
      request_id: requestID,
      status: 'failed',
      failure_reason: status,
      timestamp: new Date(),
    })

    // Check for suspicious activity (multiple failures)
    const recentFailures = await db.auth_logs.count({
      where: {
        user_email: identifier,
        auth_type: 'totp',
        status: 'failed',
        timestamp: { $gte: new Date(Date.now() - 15 * 60 * 1000) }, // Last 15 minutes
      },
    })

    if (recentFailures >= 5) {
      // Alert security team
      await sendSecurityAlert(
        identifier,
        'Multiple TOTP authentication failures',
      )
    }

    console.log(`TOTP authentication failed for ${identifier}: ${status}`)
  } catch (error) {
    console.error(`Failed to process TOTP failure for ${identifier}:`, error)
  }
}

// Start server
app.listen(3000, () => {
  console.log('Webhook server running on port 3000')
})

Callback URL Parameters

  • Name
    status
    Type
    string
    Description

    Result status: "success", "cancelled", "expired", or "error"

  • Name
    identifier
    Type
    string
    Description

    User identifier that was used in the setup/authentication

  • Name
    totpID
    Type
    string
    Description

    Present for setup completion - the TOTP user ID for future reference

  • Name
    requestID
    Type
    string
    Description

    Present for authentication completion - the authentication request ID

  • Name
    reason
    Type
    string
    Description

    Present for cancelled/error status - describes why the process failed

  • Name
    timestamp
    Type
    string
    Description

    ISO timestamp when the event occurred

Mobile App Integration

For mobile applications, hosted integration works seamlessly with both external browser and WebView approaches. Mobile users are automatically redirected using deep links.

Deep Link Configuration

Configure your mobile app to handle deep link returns from hosted pages.

Mobile Deep Link Setup

// React Native deep link integration

import { Linking } from 'react-native'
import { useEffect } from 'react'

const TOTP_PIPELINE_ID = 'your_pipeline_id_here'

export function useTOTPIntegration() {
  // Handle deep link returns
  useEffect(() => {
    const handleUrl = (event) => {
      handleTOTPDeepLink(event.url)
    }

    // Listen for deep links when app is open
    const subscription = Linking.addEventListener('url', handleUrl)

    // Handle deep link if app was launched from one
    Linking.getInitialURL().then((url) => {
      if (url) {
        handleTOTPDeepLink(url)
      }
    })

    return () => subscription?.remove()
  }, [])

  const setupTOTP = async (userEmail) => {
    const setupURL = `https://app.akedly.io/totp/setup?pipeline=${TOTP_PIPELINE_ID}&identifier=${encodeURIComponent(userEmail)}`

    const supported = await Linking.canOpenURL(setupURL)
    if (supported) {
      await Linking.openURL(setupURL)
    } else {
      throw new Error('Cannot open TOTP setup URL')
    }
  }

  const authenticateWithTOTP = async (userEmail) => {
    try {
      // Create auth request
      const response = await fetch(
        'https://api.akedly.io/api/v1/totp/request-auth',
        {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({
            APIKey: 'your_api_key_here',
            identifier: userEmail,
            pipelineID: 'your_pipeline_id_here',
          }),
        },
      )

      const data = await response.json()

      if (data.status === 'success') {
        const authRequestID = data.data.authRequestID
        const authURL = `https://app.akedly.io/totp/request?requestID=${authRequestID}`

        await Linking.openURL(authURL)
      } else {
        throw new Error(data.message)
      }
    } catch (error) {
      console.error('TOTP authentication failed:', error)
      throw error
    }
  }

  const handleTOTPDeepLink = (url) => {
    if (url.includes('totp-complete')) {
      const params = new URLSearchParams(url.split('?')[1])
      const status = params.get('status')
      const identifier = params.get('identifier')
      const totpID = params.get('totpID')
      const requestID = params.get('requestID')

      if (status === 'success') {
        if (totpID) {
          onTOTPSetupComplete(identifier, totpID)
        } else if (requestID) {
          onTOTPAuthComplete(identifier, requestID)
        }
      } else {
        onTOTPError(status, params.get('reason'))
      }
    }
  }

  const onTOTPSetupComplete = (identifier, totpID) => {
    // Handle successful setup
    console.log('TOTP setup completed:', { identifier, totpID })
    // Update app state, show success message, navigate to settings
  }

  const onTOTPAuthComplete = (identifier, requestID) => {
    // Handle successful authentication
    console.log('TOTP auth completed:', { identifier, requestID })
    // Complete login, navigate to main app
  }

  const onTOTPError = (status, reason) => {
    // Handle errors
    console.log('TOTP error:', { status, reason })
    // Show error message, return to previous screen
  }

  return {
    setupTOTP,
    authenticateWithTOTP,
  }
}

// Usage in component
function SettingsScreen() {
  const { setupTOTP } = useTOTPIntegration()
  const userEmail = 'user@example.com' // Get from context/state

  return (
    <View>
      <Button
        title="Enable Two-Factor Authentication"
        onPress={() => setupTOTP(userEmail)}
      />
    </View>
  )
}

Mobile Deep Link URL Schemes

Configure your mobile app's URL schemes for deep link returns:

iOS (Info.plist):

<key>CFBundleURLTypes</key>
<array>
  <dict>
    <key>CFBundleURLName</key>
    <string>com.yourapp.totp</string>
    <key>CFBundleURLSchemes</key>
    <array>
      <string>yourapp</string>
    </array>
  </dict>
</array>

Android (AndroidManifest.xml):

<activity android:name=".MainActivity">
  <intent-filter android:autoVerify="true">
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    <data android:scheme="yourapp" />
  </intent-filter>
</activity>

Error Handling

Hosted integration automatically handles most error scenarios, but you should implement proper callback handling for edge cases.

Common Error Scenarios

Error Handling Implementation

// Error handling for hosted TOTP integration

class TOTPErrorHandler {
  static handleCallback(url) {
    const params = new URLSearchParams(url.split('?')[1])
    const status = params.get('status')
    const reason = params.get('reason')
    const identifier = params.get('identifier')

    switch (status) {
      case 'success':
        return this.handleSuccess(params)

      case 'cancelled':
        return this.handleCancellation(reason)

      case 'expired':
        return this.handleExpiration(identifier)

      case 'error':
        return this.handleError(reason, identifier)

      default:
        return this.handleUnknownStatus(status)
    }
  }

  static handleSuccess(params) {
    const totpID = params.get('totpID')
    const requestID = params.get('requestID')
    const identifier = params.get('identifier')

    if (totpID) {
      // Setup success
      return {
        type: 'setup_success',
        message: 'Two-factor authentication has been set up successfully!',
        action: () => this.updateTOTPStatus(identifier, true),
        redirect: '/settings/security',
      }
    } else if (requestID) {
      // Auth success
      return {
        type: 'auth_success',
        message: 'Authentication successful!',
        action: () => this.completeAuthentication(identifier),
        redirect: this.getIntendedUrl() || '/dashboard',
      }
    }

    return {
      type: 'unknown_success',
      message: 'Operation completed successfully.',
      redirect: '/',
    }
  }

  static handleCancellation(reason) {
    const messages = {
      user_cancelled: 'You cancelled the authentication process.',
      browser_closed: 'The authentication window was closed.',
      timeout: 'The authentication process timed out.',
    }

    return {
      type: 'cancelled',
      message: messages[reason] || 'Authentication was cancelled.',
      action: () => this.showRetryOption(),
      redirect: null, // Stay on current page
    }
  }

  static handleExpiration(identifier) {
    return {
      type: 'expired',
      message: 'The authentication request expired. Please try again.',
      action: () => this.offerRetry(identifier),
      redirect: null,
    }
  }

  static handleError(reason, identifier) {
    const errorMessages = {
      totp_not_setup: {
        message: 'Two-factor authentication is not set up for this account.',
        action: () => this.redirectToSetup(identifier),
        redirect: '/settings/security',
      },
      quota_exceeded: {
        message: 'Account quota exceeded. Please contact support.',
        action: () => this.contactSupport(),
        redirect: '/support',
      },
      invalid_pipeline: {
        message: 'Configuration error. Please contact support.',
        action: () => this.logConfigError(),
        redirect: '/support',
      },
      network_error: {
        message:
          'Network error occurred. Please check your connection and try again.',
        action: () => this.showRetryOption(),
        redirect: null,
      },
    }

    const errorInfo = errorMessages[reason] || {
      message: 'An unexpected error occurred. Please try again.',
      action: () => console.log('Unknown error:', reason),
      redirect: null,
    }

    return {
      type: 'error',
      ...errorInfo,
    }
  }

  static handleUnknownStatus(status) {
    console.error('Unknown TOTP callback status:', status)
    return {
      type: 'unknown',
      message: 'An unexpected response was received. Please try again.',
      redirect: '/',
    }
  }

  // Helper methods
  static updateTOTPStatus(identifier, enabled) {
    // Update user's TOTP status in your system
    fetch('/api/users/totp-status', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ identifier, enabled }),
    })
  }

  static completeAuthentication(identifier) {
    // Complete login process
    sessionStorage.setItem('authenticated', 'true')
    sessionStorage.setItem('user_email', identifier)
  }

  static getIntendedUrl() {
    return sessionStorage.getItem('intended_url')
  }

  static showRetryOption() {
    // Show UI element allowing user to retry
    const retryBtn = document.getElementById('retry-totp')
    if (retryBtn) {
      retryBtn.style.display = 'block'
    }
  }

  static offerRetry(identifier) {
    if (
      confirm(
        'The authentication request expired. Would you like to try again?',
      )
    ) {
      // Restart authentication process
      window.location.href = `/totp/auth?email=${encodeURIComponent(identifier)}`
    }
  }

  static redirectToSetup(identifier) {
    window.location.href = `/totp/setup?email=${encodeURIComponent(identifier)}`
  }

  static contactSupport() {
    window.location.href = '/support'
  }

  static logConfigError() {
    console.error('TOTP configuration error - invalid pipeline')
    // Send error report to your logging service
  }
}

// Usage
document.addEventListener('DOMContentLoaded', () => {
  if (window.location.search.includes('status=')) {
    const result = TOTPErrorHandler.handleCallback(window.location.href)

    // Execute action if provided
    if (result.action) {
      result.action()
    }

    // Show message to user
    if (result.message) {
      showMessage(result.message, result.type)
    }

    // Redirect if specified
    if (result.redirect) {
      setTimeout(() => {
        window.location.href = result.redirect
      }, 2000)
    }
  }
})

function showMessage(message, type) {
  const messageDiv = document.createElement('div')
  messageDiv.className = `message ${type}`
  messageDiv.textContent = message
  document.body.insertBefore(messageDiv, document.body.firstChild)

  // Auto-remove after 5 seconds
  setTimeout(() => messageDiv.remove(), 5000)
}

Rate Limiting and Quota Handling

Hosted pages automatically handle rate limiting and quota exhaustion, but you should be prepared for these scenarios in your callbacks.

Testing & Go Live

Testing Your Integration

Testing Implementation

// Comprehensive testing for hosted TOTP integration

class TOTPIntegrationTester {
  constructor(pipelineId, apiKey) {
    this.pipelineId = pipelineId
    this.apiKey = apiKey
    this.testResults = []
  }

  async runAllTests() {
    console.log('🧪 Starting TOTP Hosted Integration Tests...\n')

    await this.testPipelineConfiguration()
    await this.testSetupFlow()
    await this.testAuthenticationFlow()
    await this.testCallbackHandling()
    await this.testErrorScenarios()
    await this.testMobileIntegration()

    this.printResults()
  }

  async testPipelineConfiguration() {
    console.log('Testing Pipeline Configuration...')

    try {
      // Test pipeline exists and is accessible
      const testURL = `https://app.akedly.io/totp/setup?pipeline=${this.pipelineId}&identifier=test@example.com`

      const response = await fetch(testURL, { method: 'HEAD' })

      if (response.status === 200) {
        this.testResults.push({
          test: 'Pipeline Configuration',
          status: 'PASS',
        })
      } else if (response.status === 404) {
        this.testResults.push({
          test: 'Pipeline Configuration',
          status: 'FAIL',
          error: 'Pipeline not found',
        })
      } else {
        this.testResults.push({
          test: 'Pipeline Configuration',
          status: 'FAIL',
          error: `HTTP ${response.status}`,
        })
      }
    } catch (error) {
      this.testResults.push({
        test: 'Pipeline Configuration',
        status: 'ERROR',
        error: error.message,
      })
    }
  }

  async testSetupFlow() {
    console.log('Testing Setup Flow...')

    try {
      // Test setup URL generation
      const setupURL = `https://app.akedly.io/totp/setup?pipeline=${this.pipelineId}&identifier=test-setup@example.com`

      if (
        setupURL.includes(this.pipelineId) &&
        setupURL.includes('test-setup@example.com')
      ) {
        this.testResults.push({ test: 'Setup URL Generation', status: 'PASS' })
      } else {
        this.testResults.push({ test: 'Setup URL Generation', status: 'FAIL' })
      }

      // Test callback URL handling
      const callbackURL =
        'https://yourapp.com/totp-callback?status=success&identifier=test-setup@example.com&totpID=test123'
      const result = this.simulateCallback(callbackURL)

      if (result.type === 'setup_success') {
        this.testResults.push({
          test: 'Setup Callback Handling',
          status: 'PASS',
        })
      } else {
        this.testResults.push({
          test: 'Setup Callback Handling',
          status: 'FAIL',
        })
      }
    } catch (error) {
      this.testResults.push({
        test: 'Setup Flow',
        status: 'ERROR',
        error: error.message,
      })
    }
  }

  async testAuthenticationFlow() {
    console.log('Testing Authentication Flow...')

    try {
      // Test auth request creation
      const response = await fetch(
        'https://api.akedly.io/api/v1/totp/request-auth',
        {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({
            APIKey: this.apiKey,
            identifier: 'test-auth@example.com',
            pipelineID: this.pipelineId,
          }),
        },
      )

      const data = await response.json()

      if (data.status === 'success' && data.data.authRequestID) {
        this.testResults.push({ test: 'Auth Request Creation', status: 'PASS' })

        // Test auth URL generation
        const authURL = `https://app.akedly.io/totp/request?requestID=${data.data.authRequestID}`

        if (authURL.includes(data.data.authRequestID)) {
          this.testResults.push({ test: 'Auth URL Generation', status: 'PASS' })
        } else {
          this.testResults.push({ test: 'Auth URL Generation', status: 'FAIL' })
        }
      } else {
        this.testResults.push({
          test: 'Auth Request Creation',
          status: 'FAIL',
          error: data.message,
        })
      }
    } catch (error) {
      this.testResults.push({
        test: 'Authentication Flow',
        status: 'ERROR',
        error: error.message,
      })
    }
  }

  async testCallbackHandling() {
    console.log('Testing Callback Handling...')

    const testCases = [
      {
        name: 'Success Setup Callback',
        url: 'https://yourapp.com/callback?status=success&identifier=user@example.com&totpID=abc123',
        expectedType: 'setup_success',
      },
      {
        name: 'Success Auth Callback',
        url: 'https://yourapp.com/callback?status=success&identifier=user@example.com&requestID=req123',
        expectedType: 'auth_success',
      },
      {
        name: 'Cancelled Callback',
        url: 'https://yourapp.com/callback?status=cancelled&reason=user_cancelled',
        expectedType: 'cancelled',
      },
      {
        name: 'Expired Callback',
        url: 'https://yourapp.com/callback?status=expired&identifier=user@example.com',
        expectedType: 'expired',
      },
      {
        name: 'Error Callback',
        url: 'https://yourapp.com/callback?status=error&reason=quota_exceeded',
        expectedType: 'error',
      },
    ]

    testCases.forEach((testCase) => {
      try {
        const result = this.simulateCallback(testCase.url)

        if (result.type === testCase.expectedType) {
          this.testResults.push({ test: testCase.name, status: 'PASS' })
        } else {
          this.testResults.push({
            test: testCase.name,
            status: 'FAIL',
            error: `Expected ${testCase.expectedType}, got ${result.type}`,
          })
        }
      } catch (error) {
        this.testResults.push({
          test: testCase.name,
          status: 'ERROR',
          error: error.message,
        })
      }
    })
  }

  async testErrorScenarios() {
    console.log('Testing Error Scenarios...')

    // Test invalid pipeline ID
    const invalidURL =
      'https://app.akedly.io/totp/setup?pipeline=invalid&identifier=test@example.com'

    try {
      const response = await fetch(invalidURL, { method: 'HEAD' })

      if (response.status === 404 || response.status === 400) {
        this.testResults.push({
          test: 'Invalid Pipeline Handling',
          status: 'PASS',
        })
      } else {
        this.testResults.push({
          test: 'Invalid Pipeline Handling',
          status: 'FAIL',
        })
      }
    } catch (error) {
      this.testResults.push({
        test: 'Invalid Pipeline Handling',
        status: 'ERROR',
        error: error.message,
      })
    }
  }

  async testMobileIntegration() {
    console.log('Testing Mobile Integration...')

    // Test mobile redirect URL format
    const mobileCallbackURL =
      'myapp://totp-complete?status=success&identifier=user@example.com&totpID=abc123'

    try {
      const result = this.simulateCallback(mobileCallbackURL)

      if (result.type === 'setup_success') {
        this.testResults.push({
          test: 'Mobile Deep Link Callback',
          status: 'PASS',
        })
      } else {
        this.testResults.push({
          test: 'Mobile Deep Link Callback',
          status: 'FAIL',
        })
      }
    } catch (error) {
      this.testResults.push({
        test: 'Mobile Deep Link Callback',
        status: 'ERROR',
        error: error.message,
      })
    }
  }

  simulateCallback(url) {
    // Simulate TOTPErrorHandler.handleCallback for testing
    const params = new URLSearchParams(url.split('?')[1])
    const status = params.get('status')
    const totpID = params.get('totpID')
    const requestID = params.get('requestID')

    if (status === 'success') {
      if (totpID) {
        return { type: 'setup_success' }
      } else if (requestID) {
        return { type: 'auth_success' }
      }
    }

    return { type: status || 'unknown' }
  }

  printResults() {
    console.log('\n📊 Test Results:')
    console.log('==================')

    this.testResults.forEach((result) => {
      const emoji =
        result.status === 'PASS' ? '✅' : result.status === 'FAIL' ? '❌' : '⚠️'
      console.log(`${emoji} ${result.test}: ${result.status}`)
      if (result.error) {
        console.log(`   Error: ${result.error}`)
      }
    })

    const passCount = this.testResults.filter((r) => r.status === 'PASS').length
    const totalCount = this.testResults.length

    console.log(`\nResults: ${passCount}/${totalCount} tests passed`)

    if (passCount === totalCount) {
      console.log('🎉 All tests passed! Ready for production.')
    } else {
      console.log('⚠️ Some tests failed. Please review before going live.')
    }
  }
}

// Run tests
const tester = new TOTPIntegrationTester('your_pipeline_id', 'your_api_key')
await tester.runAllTests()

Testing Checklist

Setup Flow Testing:

  • Pipeline configuration loads correctly
  • Setup URLs generate with correct parameters
  • Hosted setup page displays with your branding
  • QR codes work with different authenticator apps
  • Deep links open authenticator apps on mobile
  • Manual setup keys work as fallback
  • Success callbacks return to your app correctly

Authentication Flow Testing:

  • Auth requests create successfully
  • Hosted auth page displays with your branding
  • Valid TOTP codes authenticate successfully
  • Invalid codes show appropriate error messages
  • Expired requests are handled correctly
  • Success callbacks return to your app correctly

Error Scenario Testing:

  • Invalid pipeline IDs are handled
  • Network errors show appropriate messages
  • Rate limiting displays countdown timers
  • Quota exhaustion shows support contact info
  • User cancellation returns to your app

Mobile Testing:

  • Deep links are configured correctly
  • Mobile detection works on hosted pages
  • External browser flow works
  • Deep link returns work
  • WebView integration works (if using)

Go Live Checklist

Configuration:

  • Switch from Test to Live pipeline type
  • Configure production callback URLs
  • Test webhook endpoints in production environment

Monitoring:

  • Set up error logging for callbacks
  • Monitor authentication success rates
  • Set up alerts for high failure rates
  • Track user adoption metrics

Documentation:

  • Document integration for your team
  • Provide user help documentation
  • Set up support processes for TOTP issues

Summary

Hosted integration provides the fastest path to implementing TOTP authentication with zero UI development required. By leveraging Akedly's hosted pages, you get:

Complete white-label experience with your company branding
Cross-platform compatibility with automatic mobile/desktop detection
Comprehensive error handling for all edge cases
Webhook integration for real-time server notifications
Mobile app support with deep links and external browser flow

The hosted approach handles all the complex UI states, device detection, error scenarios, and security considerations automatically, allowing you to focus on your core application logic.

Was this page helpful?