Skip to main content

Common Issues

Authentication Token Issues

Problem: Backend returns an error when calling Doshi API.Possible Causes:
  • Invalid API key
  • API key not provided in Authorization header
  • Invalid request parameters
  • Network connectivity issues
Solutions:
  1. Verify API key:
    # Test your API key
    curl -X POST https://api.doshi.app/client/auth/token \
      -H "Authorization: Bearer YOUR_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{"email": "[email protected]"}'
  1. Check environment variables:
    // Verify API key is loaded
    console.log('API Key exists:', !!process.env.DOSHI_API_KEY);
    console.log('API Key length:', process.env.DOSHI_API_KEY?.length);
    // Don't log the actual key!
  1. Validate request parameters:
    // Ensure you're sending email OR partnerUserId
    const requestBody = {
      email: user.email || undefined,
      partnerUserId: user.id || undefined
    };
    
    if (!requestBody.email && !requestBody.partnerUserId) {
      throw new Error('Must provide email or partnerUserId');
    }
  1. Check API response:
    const response = await fetch(doshiApiUrl, options);
    
    if (!response.ok) {
      const errorText = await response.text();
      console.error('API Error:', response.status, errorText);
      throw new Error(`API returned ${response.status}: ${errorText}`);
    }
Problem: Token is rejected by the iframe.Possible Causes:
  • Token was generated too long ago
  • Token was already used
  • Token format is incorrect
Solutions:
  1. Generate token immediately before use:
    // โœ… Generate token right before passing to iframe
    const token = await fetchToken();
    passToIframe(token);

    // โŒ Don't store tokens for long periods
    const token = await fetchToken();
    localStorage.setItem('token', token); // Don't do this
  1. Donโ€™t reuse tokens:
    // Each iframe load should get a fresh token
    useEffect(() => {
      const fetchAndSetToken = async () => {
        const newToken = await fetchToken();
        setToken(newToken);
      };
      
      fetchAndSetToken();
    }, []); // Fresh token on mount
  1. Verify token format:
    const isValidToken = (token: string) => {
      return token && 
             typeof token === 'string' && 
             token.length > 0 &&
             !token.includes(' '); // No spaces
    };

    if (!isValidToken(token)) {
      console.error('Invalid token format');
    }
Problem: API key is visible in client-side code.Danger: Anyone can steal your API key and impersonate your application.Solution:
  1. Move API calls to backend:
    // โŒ NEVER do this in frontend
    const response = await fetch(doshiApiUrl, {
      headers: {
        'Authorization': `Bearer ${API_KEY}` // EXPOSED!
      }
    });

    // โœ… Call your backend instead
    const response = await fetch('/api/generate-doshi-token', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({ userId: user.id })
    });
  1. Create backend endpoint:
    // backend/routes/auth.ts
    app.post('/api/generate-doshi-token', async (req, res) => {
      // Verify user is authenticated
      if (!req.session?.userId) {
        return res.status(401).json({ error: 'Unauthorized' });
      }

      // Call Doshi API with server-side API key
      const response = await fetch(doshiApiUrl, {
        headers: {
          'Authorization': `Bearer ${process.env.DOSHI_API_KEY}`
        }
      });

      const data = await response.json();
      res.json({ token: data.token });
    });

postMessage Issues

Problem: The iframe is not receiving messages from the parent window.Solutions:
  1. Verify origin:
    window.addEventListener("message", (event) => {
      console.log('Message received from:', event.origin);
      console.log('Expected origin:', 'https://embed.doshi.app');
      
      // Temporarily disable check for debugging
      // if (event.origin !== 'https://embed.doshi.app') return;
      
      console.log('Message data:', event.data);
    });
  1. Check iframe is loaded:
    const iframe = document.querySelector('iframe');
    
    iframe.addEventListener('load', () => {
      console.log('โœ… Iframe loaded');
      // Now it's safe to send messages
    });
  1. Verify message format:
    // Make sure you're sending proper JSON
    const message = JSON.stringify({
      token: authToken,
      email: userEmail,
      type: "AUTH"
    });
    
    console.log('Sending message:', message);
    iframe.contentWindow.postMessage(message, 'https://embed.doshi.app');
  1. Check for JavaScript errors:
    window.addEventListener('error', (event) => {
      console.error('JavaScript error:', event.error);
    });
Problem: Messages are rejected due to origin mismatch.Solutions:
  1. Log actual origins:
    window.addEventListener("message", (event) => {
      console.log('Expected:', 'https://embed.doshi.app');
      console.log('Received:', event.origin);
      console.log('Match:', event.origin === 'https://embed.doshi.app');
    });
  1. Check for trailing slashes:
    // Normalize origin comparison
    const normalizeOrigin = (origin) => origin.replace(/\/$/, '');
    
    if (normalizeOrigin(event.origin) !== normalizeOrigin(expectedOrigin)) {
      return;
    }
  1. Handle multiple environments:
    const allowedOrigins = [
      'https://embed.doshi.app',
      'https://staging-embed.doshi.app',
      'http://localhost:3000' // Development only
    ];
    
    if (!allowedOrigins.includes(event.origin)) {
      console.warn('Rejected message from:', event.origin);
      return;
    }
Problem: Parent window never receives PING from iframe.Solutions:
  1. Check iframe URL:
    const iframe = document.querySelector('iframe');
    console.log('Iframe src:', iframe?.src);
    // Should be: https://embed.doshi.app
  1. Wait for iframe to load:
    const iframe = document.querySelector('iframe');
    
    iframe.addEventListener('load', () => {
      console.log('Iframe loaded, waiting for PING...');
    });

    // Set a timeout to detect if PING never arrives
    setTimeout(() => {
      console.warn('No PING received after 5 seconds');
    }, 5000);
  1. Check for errors in iframe:
    iframe.addEventListener('error', (e) => {
      console.error('Iframe failed to load:', e);
    });

Query Parameter Issues

Problem: Query parameters are missing from the iframe URL.Solutions:
  1. Debug URL construction:
    const params = new URLSearchParams({
      token: authToken,
      email: userEmail
    });
    
    const fullUrl = `https://embed.doshi.app?${params.toString()}`;
    
    console.log('Constructed URL:', fullUrl);
    console.log('URL length:', fullUrl.length);
    
    // Verify iframe gets the URL
    const iframe = document.querySelector('iframe');
    console.log('Iframe src:', iframe?.src);
  1. Check for undefined values:
    const params = new URLSearchParams();
    
    // Only add defined values
    if (authToken) params.append('token', authToken);
    if (userEmail) params.append('email', userEmail);
    
    console.log('Params:', Object.fromEntries(params));
Problem: URL exceeds browser limits (typically 2000 characters).Solutions:
  1. Check URL length:
    const url = buildWebviewUrl(baseUrl, params);
    
    if (url.length > 2000) {
      console.error('URL too long:', url.length, 'characters');
      // Switch to postMessage method
    }
  1. Reduce parameter size:
    // Only include essential parameters
    const minimalParams = {
      token: authToken,
      email: userEmail
      // Fetch other data from API after auth
    };
  1. Switch to postMessage:
    // If URL is too long, use postMessage instead
    if (estimatedUrlLength > 2000) {
      return <WebviewWithPostMessage {...props} />;
    }
    return <WebviewWithQueryParams {...props} />;
Problem: Special characters not encoding properly.Solutions:
  1. Use URLSearchParams:
    // โœ… Automatic encoding
    const params = new URLSearchParams();
    params.append('email', '[email protected]');
    params.append('name', 'John & Jane');
    
    // โŒ Manual concatenation
    const url = `${baseUrl}[email protected]`; // Wrong!
  1. Verify encoding:
    const params = new URLSearchParams({ email: '[email protected]' });
    console.log('Encoded:', params.toString());
    // Should be: email=user%2Btest%40example.com

2FA Issues

Problem: 2FA is enabled but OTP screen doesnโ€™t appear.Solutions:
  1. Verify 2FA flag:
    const authData = {
      token: authToken,
      is2FaEnabled: true, // Must be boolean true, not string
      dob: '1990-01-15',
      organizationId: 'org_123',
      // ... other required fields
    };
    
    console.log('2FA enabled:', authData.is2FaEnabled === true);
  1. Check all required fields:
    const required2FAFields = [
      'dob',
      'organizationId',
      'partnerUserId',
      'firstName',
      'lastName'
    ];
    
    const missing = required2FAFields.filter(field => !authData[field]);
    
    if (missing.length > 0) {
      console.error('Missing 2FA fields:', missing);
    }
  1. Verify organization has 2FA enabled:
    // Check with your account manager if 2FA is enabled
    // for your organization in Doshi
Problem: User doesnโ€™t receive the OTP code.Solutions:
  1. Verify phone number:
    // Ensure phone number is registered in Doshi
    // Contact [email protected] if needed
  1. Check OTP channel:
    // If SMS not working, user can try different channels
    // The iframe will handle this automatically
  1. Wait and retry:
    // OTP may take 30-60 seconds to arrive
    // User can request resend from the iframe
Problem: Correct OTP code is rejected.Solutions:
  1. Check OTP expiration:
    // OTPs typically expire after 5-10 minutes
    // User should request a new OTP
  1. Verify user details match:
    // All user details must match exactly
    // (name, DOB, organization ID, partner user ID)

Browser Compatibility

Problem: postMessage API not supported.Solution:
    // Check for support
    if (typeof window.postMessage === 'undefined') {
      console.error('postMessage not supported');
      // Fallback to query parameters
      return <WebviewWithQueryParams {...props} />;
    }
Problem: URLSearchParams not supported in older browsers.Solution:
    // Check for support and provide fallback
    function buildQueryString(params) {
      if (typeof URLSearchParams !== 'undefined') {
        return new URLSearchParams(params).toString();
      }
      
      // Fallback: manual encoding
      return Object.entries(params)
        .map(([key, value]) => 
          `${encodeURIComponent(key)}=${encodeURIComponent(value)}`
        )
        .join('&');
    }
Problem: Iframe doesnโ€™t load on iOS Safari.Solutions:
  1. Check iframe attributes:
    <iframe
      src="https://embed.doshi.app"
      allow="fullscreen"
      sandbox="allow-same-origin allow-scripts allow-forms"
    />
  1. Ensure HTTPS:
    // iOS Safari requires HTTPS for iframes
    const url = "https://embed.doshi.app"; // Must be HTTPS

Mobile WebView Issues

Problem: When keyboard opens, content gets squeezed and becomes not visible.Solution: Handle keyboard visibility in your app wrapper.iOS:
    // Adjust WebView constraints when keyboard shows
    NotificationCenter.default.addObserver(
        self,
        selector: #selector(keyboardWillShow),
        name: UIResponder.keyboardWillShowNotification,
        object: nil
    )
Android:
    <!-- In AndroidManifest.xml -->
    <activity
        android:name=".WebViewActivity"
        android:windowSoftInputMode="adjustResize">
    </activity>
See Best Practices - Mobile Integration for complete code.
Problem: Users can pinch-to-zoom and it breaks the layout.Solution: Disable zoom in your WebView.Web:
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
iOS:
    webView.scrollView.isScrollEnabled = false
    webView.scrollView.bounces = false
Android:
    webView.settings.apply {
        setSupportZoom(false)
        builtInZoomControls = false
    }
See Best Practices - Disable Zoom for details.
Problem: Doshi Frontend doesnโ€™t load in mobile WebView.Solutions:
  1. Enable JavaScript:
    // Android
    webView.settings.javaScriptEnabled = true
    webView.settings.domStorageEnabled = true
    // iOS
    let preferences = WKPreferences()
    preferences.javaScriptEnabled = true
    webConfiguration.preferences = preferences
  1. Check HTTPS:
  • Ensure youโ€™re loading https://embed.doshi.app
  • iOS requires HTTPS by default (App Transport Security)
  1. Check App Permissions:
    <!-- Android: In AndroidManifest.xml -->
    <uses-permission android:name="android.permission.INTERNET" />
    <!-- iOS: In Info.plist (if using HTTP for testing) -->
    <key>NSAppTransportSecurity</key>
    <dict>
        <key>NSAllowsArbitraryLoads</key>
        <true/>
    </dict>

Network Issues

Problem: Authentication takes too long.Solutions:
  1. Add timeout:
    const AUTH_TIMEOUT = 10000; // 10 seconds
    
    const authenticateWithTimeout = () => {
      return Promise.race([
        authenticate(),
        new Promise((_, reject) => 
          setTimeout(() => reject(new Error('Timeout')), AUTH_TIMEOUT)
        )
      ]);
    };
  1. Show loading state:
    <div className="loading">
      <Spinner />
      <p>This may take a moment...</p>
    </div>
  1. Preconnect to domain:
    <link rel="preconnect" href="https://embed.doshi.app">
    <link rel="dns-prefetch" href="https://embed.doshi.app">
Problem: Authentication works sometimes but fails randomly.Solutions:
  1. Implement retry logic:
    const authenticateWithRetry = async (maxRetries = 3) => {
      for (let i = 0; i < maxRetries; i++) {
        try {
          return await authenticate();
        } catch (error) {
          if (i === maxRetries - 1) throw error;
          await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
        }
      }
    };
  1. Check network status:
    if (!navigator.onLine) {
      setError('No internet connection');
      return;
    }
    
    window.addEventListener('online', () => {
      console.log('Connection restored');
      retry();
    });

IP Whitelisting Issues

Problem: Receiving 403 Forbidden when calling Doshi API.Cause: Your server IP is not whitelisted.Solution:
  1. Find your server IP:
    curl ifconfig.me
  1. Email [email protected] with:
    • Your organization name
    • Your server IP address
    • Whether itโ€™s for production or staging
  2. Wait for confirmation that your IP is whitelisted
Problem: API works in staging but not production (or vice versa).Cause: Different servers have different IPs.Solution:Get the IP from each server and have all of them whitelisted:
    # Run on each server
    curl ifconfig.me
Contact [email protected] with all IPs.

Debugging Tools

Enable Debug Logging

// config.ts
export const DEBUG = process.env.NODE_ENV === 'development';

// utils/logger.ts
export const logger = {
  debug: (...args: any[]) => {
    if (DEBUG) {
      console.log('[Doshi Debug]', ...args);
    }
  },
  
  error: (...args: any[]) => {
    console.error('[Doshi Error]', ...args);
  }
};

// Usage
logger.debug('Sending auth message:', authData);
logger.error('Authentication failed:', error);

Message Inspector

// Log all messages for debugging
window.addEventListener("message", (event) => {
  console.group('๐Ÿ“จ Message Received');
  console.log('Origin:', event.origin);
  console.log('Data:', event.data);
  console.log('Type:', typeof event.data);
  console.log('Timestamp:', new Date().toISOString());
  console.groupEnd();
}, true); // Use capture phase

Network Monitor

// Monitor all fetch requests
const originalFetch = window.fetch;
window.fetch = async (...args) => {
  console.log('๐ŸŒ Fetch:', args[0]);
  const response = await originalFetch(...args);
  console.log('โœ… Response:', response.status, args[0]);
  return response;
};

Check iframe Status

function debugIframe() {
  const iframe = document.querySelector('iframe');
  
  console.log('Iframe found:', !!iframe);
  console.log('Iframe src:', iframe?.src);
  console.log('Iframe loaded:', iframe?.contentWindow !== null);
  console.log('Can access contentWindow:', {
    exists: !!iframe?.contentWindow,
    canPostMessage: typeof iframe?.contentWindow?.postMessage === 'function'
  });
}

Testing Checklist

Test with valid credentials
Test with invalid credentials
Test with missing token
Test with expired token
Test 2FA flow (if enabled)
Test on Chrome
Test on Firefox
Test on Safari
Test on Edge
Test on mobile Safari (iOS)
Test on mobile Chrome (Android)
Test with slow network (throttling)
Test with offline/online transitions
Test timeout scenarios
Test error recovery

Getting Help

If youโ€™re still experiencing issues after trying these solutions:

Contact Support

Email us at [email protected] with:
  • Detailed description of the issue
  • Browser and OS information
  • Any error messages
  • Steps to reproduce

Check API Status

Verify Doshi services are operational

Quick Fixes Reference

IssueQuick Fix
Messages not receivedCheck event.origin validation
Token not workingGenerate fresh token, donโ€™t reuse
API key exposedMove to backend immediately
URL too longSwitch to postMessage
2FA not showingVerify is2FaEnabled: true and all required fields
Slow loadingAdd preconnect, implement timeout
Origin mismatchLog and compare actual vs expected origins
CORS errorsEnsure all URLs use HTTPS
Iframe not loadingCheck src attribute and network tab

Next Steps