Troubleshooting Guide
Common issues and solutions for OpenDev platform integration.
Authentication Issues
Issue: OAuth Login Fails with "Invalid Client"
Symptoms:
- Error message:
invalidclientorclientid is invalid - Login popup closes immediately
Solutions:
- Verify Client ID:
- Go to OpenDev Dashboard > Apps > Your App > OAuth Channels
- Confirm the Client ID matches provider console
- Check Provider Console:
- Ensure OAuth app is enabled/published
- Verify redirect URIs are correctly configured
- Check if app is in production mode (not test mode)
- For Google:
- Enable required APIs (Google+ API, People API)
- Verify OAuth consent screen is configured
- Add authorized domains
Issue: "Redirect URI Mismatch" Error
Symptoms:
- Error:
redirecturimismatch - Works in development but fails in production
Solutions:
- Check Exact Match:
Provider Console URI: https://yourdomain.com/auth/google/callback
OpenDev Config URI: https://yourdomain.com/auth/google/callback
Note: Trailing slashes matter!
- Protocol Issues:
❌ http://yourdomain.com/callback (No HTTPS)
✅ https://yourdomain.com/callback (HTTPS required in production)
- Multiple Environments:
// Development
"callbackUrl": "http://localhost:3000/auth/google/callback"
// Production
"callbackUrl": "https://yourdomain.com/auth/google/callback"
Issue: Apple Sign In Not Working on Web
Symptoms:
- Popup appears but closes without completing
- Error:
invalid_request
Solutions:
- Service ID Configuration:
- Create separate Service ID for web (not App ID)
- Configure domains and return URLs
- Verify private key is associated with Service ID
- Domain Verification:
- Download apple-developer-domain-association.txt
- Place at: https://yourdomain.com/.well-known/
- Verify domain in Apple Developer Console
- Private Key Format:
- Must be PEM format with headers
- Store securely (never in client code)
- Check key hasn't expired
Payment Issues
Issue: Stripe Payment Intent Fails
Symptoms:
- Error:
paymentintentunexpected_state - Payment stuck in "processing"
Solutions:
- Check Payment Intent Status:
const intent = await stripe.paymentIntents.retrieve(intentId);
console.log('Status:', intent.status);
console.log('Last error:', intent.last_payment_error);
- Common Status Issues:
| Status | Meaning | Action |
|---|---|---|
requirespaymentmethod |
No payment method | Collect payment details |
requires_confirmation |
Needs confirmation | Call confirmPayment() |
requires_action |
3D Secure needed | Handle authentication |
canceled |
Was cancelled | Create new intent |
- 3D Secure Handling:
if (intent.status === 'requires_action') {
const { error } = await stripe.handleCardAction(intent.client_secret);
if (error) {
// Handle authentication failure
}
}
Issue: Google Play Purchase Not Verified
Symptoms:
- Purchase succeeds on device but verification fails
- Error:
The purchase token does not match the package name
Solutions:
- Verify Package Name:
Package name in Play Console: com.yourcompany.app
Package name in verification request: com.yourcompany.app
These MUST match exactly
- Service Account Permissions:
1. Create service account in Google Cloud Console
2. Add service account email to Play Console
3. Grant "View financial data" permission
4. Wait 24 hours for permissions to propagate
- Check Purchase Token:
// Token should be from purchase callback, not generated
const purchaseToken = purchase.purchaseToken; // ✅ Correct
const purchaseToken = generateToken(); // ❌ Wrong
Issue: Apple IAP Receipt Verification Fails
Symptoms:
- Status code 21007 or 21008
- Error:
shared_secret is invalid
Solutions:
- Sandbox vs Production:
// Status 21007 = Sandbox receipt sent to production
// Solution: Try sandbox endpoint
async function verifyReceipt(receipt) {
let result = await verifyWithEndpoint(receipt, PRODUCTION_URL);
if (result.status === 21007) {
result = await verifyWithEndpoint(receipt, SANDBOX_URL);
}
return result;
}
- Shared Secret:
1. Go to App Store Connect > Apps > Your App
2. Features > In-App Purchases > App-Specific Shared Secret
3. Generate and copy to OpenDev configuration
- Receipt Format:
// Receipt must be base64 encoded
const receiptData = Buffer.from(receipt).toString('base64');
SDK Issues
Issue: SDK Initialization Fails
Symptoms:
- Error:
Invalid configuration - App crashes on startup
Solutions:
- Check Configuration File:
{
"appId": "required", // ✅ Must be valid UUID
"channelId": "required", // ✅ Must exist in OpenDev
"environment": "production" // ✅ or "development"
}
- Verify Network Access:
iOS: Add App Transport Security exceptions if needed
Android: Add network permission to manifest
Web: Check CORS configuration
- Debug Mode:
OpenDev.configure({
appId: 'xxx',
debug: true,
onError: (error) => console.error('SDK Error:', error)
});
Issue: Token Refresh Fails
Symptoms:
- User gets logged out unexpectedly
- Error:
refreshtokeninvalid
Solutions:
- Check Token Storage:
// Ensure tokens are stored securely and not cleared
const storedToken = await SecureStorage.get('refresh_token');
if (!storedToken) {
// Token was cleared - need to re-login
}
- Token Expiration:
// Refresh tokens also expire (typically 30-90 days)
// Implement proactive refresh before expiration
const tokenExpiry = decodeToken(refreshToken).exp;
const now = Date.now() / 1000;
if (tokenExpiry - now < 86400) { // Less than 1 day
await refreshAuthToken();
}
- Provider-Specific Issues:
- Google: Refresh tokens may be revoked if unused for 6 months
- Apple: Refresh tokens require revalidation on privacy changes
- WeChat: Tokens expire after 30 days regardless of usage
Network Issues
Issue: API Requests Timeout
Symptoms:
- Requests hang indefinitely
- Error:
ETIMEDOUTorECONNRESET
Solutions:
- Implement Timeouts:
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 10000);
try {
const response = await fetch(url, {
signal: controller.signal
});
} finally {
clearTimeout(timeout);
}
- Retry Logic:
async function fetchWithRetry(url, options, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await fetch(url, options);
} catch (error) {
if (i === maxRetries - 1) throw error;
await sleep(Math.pow(2, i) * 1000); // Exponential backoff
}
}
}
- Check Network Configuration:
- Verify firewall rules allow outbound HTTPS
- Check proxy settings if behind corporate network
- Test with different network (mobile data vs WiFi)
Issue: CORS Errors in Browser
Symptoms:
- Error:
Access-Control-Allow-Originheader missing - Requests work in Postman but not browser
Solutions:
- Verify Allowed Origins:
OpenDev Dashboard > Apps > Your App > Settings
Add your domain to allowed origins:
- https://yourdomain.com
- http://localhost:3000 (for development)
- Preflight Requests:
// Ensure server handles OPTIONS requests
// Check that all required CORS headers are present:
// - Access-Control-Allow-Origin
// - Access-Control-Allow-Methods
// - Access-Control-Allow-Headers
- Credentials:
// If using cookies, both sides must allow credentials
fetch(url, {
credentials: 'include' // Client side
});
// Server must return:
// Access-Control-Allow-Credentials: true
Webhook Issues
Issue: Webhooks Not Being Received
Symptoms:
- No webhook events in logs
- Provider shows delivery failures
Solutions:
- Verify URL Accessibility:
# Test from external network
curl -X POST https://yourdomain.com/webhooks/stripe \
-H "Content-Type: application/json" \
-d '{"test": true}'
- Check HTTPS Certificate:
- Certificate must be valid (not self-signed)
- Certificate chain must be complete
- Certificate must not be expired
- Firewall Rules:
- Allow incoming connections on port 443
- Whitelist provider IP ranges if possible
Issue: Webhook Signature Verification Fails
Symptoms:
- Error:
Signature verification failed - Events processed but marked as invalid
Solutions:
- Raw Body Required:
// WRONG: Body already parsed
app.use(express.json());
app.post('/webhooks/stripe', (req, res) => {
// req.body is object, not raw
});
// CORRECT: Raw body for verification
app.post('/webhooks/stripe',
express.raw({ type: 'application/json' }),
(req, res) => {
// req.body is Buffer
}
);
- Secret Mismatch:
- Use webhook-specific secret (not API key)
- Ensure secret is for correct environment (test vs live)
- Don't confuse signing secret with endpoint secret
- Timestamp Tolerance:
// Webhooks have timestamp tolerance (usually 5 minutes)
// Check server time is synchronized
const tolerance = 300; // 5 minutes
Performance Issues
Issue: Slow API Responses
Symptoms:
- Response times > 2 seconds
- Intermittent timeouts
Solutions:
- Connection Pooling:
// Reuse HTTP connections
const https = require('https');
const agent = new https.Agent({
keepAlive: true,
maxSockets: 50
});
- Caching:
// Cache configuration and static data
const cache = new Map();
async function getConfig() {
if (cache.has('config')) {
return cache.get('config');
}
const config = await fetchConfig();
cache.set('config', config);
return config;
}
- Parallel Requests:
// Batch independent operations
const [user, products, config] = await Promise.all([
fetchUser(),
fetchProducts(),
fetchConfig()
]);
Getting Help
If you're still experiencing issues:
- Enable Debug Logging:
OpenDev.configure({
debug: true,
logLevel: 'verbose'
});
- Collect Information:
- SDK version
- Platform and version
- Error messages and stack traces
- Steps to reproduce
- Contact Support:
- Email: contact@zinben.com
- Include debug logs and reproduction steps
Last updated: January 2026