12 KiB
Flash Call OTP System — Setup Guide
Table of Contents
- Prerequisites
- Server Setup (PHP Backend)
- Android Caller App Setup
- Flutter Receiver App Setup
- End-to-End Testing
- Troubleshooting
1. Prerequisites
Server Requirements
- Ubuntu 20.04+ or similar Linux distribution
- PHP 8.1+ with phpredis extension
- MySQL 8.0+ or MariaDB 10.6+
- Redis 6.0+
- Nginx 1.18+
- Certbot (Let's Encrypt) for SSL
- Composer (optional, for dependency management)
Android Caller App Requirements
- Android Studio Hedgehog (2023.1.1) or newer
- Android device running Android 8.0 (API 26) or higher
- Active SIM card installed in the device
- Stable internet connection (Wi-Fi recommended)
Flutter Receiver App Requirements
- Flutter 3.16+ with Dart 3.x
- Android device for full auto-read testing
- iOS device for SMS AutoFill testing (optional)
- Xcode 15+ (for iOS builds)
2. Server Setup (PHP Backend)
2.1 Install Required Packages
sudo apt update
sudo apt install -y nginx php8.1-fpm php8.1-mysql php8.1-redis php8.1-mbstring php8.1-xml php8.1-curl mysql-server redis-server
2.2 Configure MySQL
sudo mysql_secure_installation
Log in to MySQL and create the database and user:
sudo mysql -u root -p
CREATE DATABASE otp-db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'otp-user'@'localhost' IDENTIFIED BY 'tCO1zuHWVKjTnryDDInL';
GRANT SELECT, INSERT, UPDATE, DELETE ON otp_db.* TO 'otp-user'@'localhost';
FLUSH PRIVILEGES;
EXIT;
2.3 Import Database Schema
mysql -u otp_user -p otp_db < /path/to/backend/database.sql
Verify the tables were created:
mysql -u otp_user -p otp_db -e "SHOW TABLES;"
You should see: api_logs, caller_devices, otp_requests
2.4 Configure Redis
Edit Redis configuration for security:
sudo nano /etc/redis/redis.conf
Set the following:
bind 127.0.0.1
protected-mode yes
# requirepass YOUR_REDIS_PASSWORD # Uncomment and set if needed
Restart Redis:
sudo systemctl restart redis-server
sudo systemctl enable redis-server
Test connection:
redis-cli ping
# Should return: PONG
2.5 Deploy PHP Backend
Create the web root directory:
sudo mkdir -p /var/www/otp.intaleqapp.com/public
sudo chown -R www-data:www-data /var/www/otp.intaleqapp.com
Copy the backend files:
# Copy the entire backend directory
cp -r backend/* /var/www/otp.intaleqapp.com/
# The structure should be:
# /var/www/otp.intaleqapp.com/
# ├── api/
# │ ├── request-otp.php
# │ ├── pending-call.php
# │ ├── call-done.php
# │ ├── verify-otp.php
# │ ├── register-device.php
# │ ├── pending-sms.php
# │ └── sms-done.php
# ├── includes/
# │ ├── Database.php
# │ ├── Redis.php
# │ ├── RateLimit.php
# │ ├── Auth.php
# │ └── Logger.php
# ├── config.php
# ├── .htaccess
# ├── database.sql
# ├── nginx.conf
# └── logs/
Set permissions:
sudo chown -R www-data:www-data /var/www/otp.intaleqapp.com
sudo chmod -R 755 /var/www/otp.intaleqapp.com
sudo chmod -R 775 /var/www/otp.intaleqapp.com/logs
2.6 Configure config.php
Edit the configuration file with your production credentials:
sudo nano /var/www/otp.intaleqapp.com/config.php
Update these critical values:
define('DB_PASS', 'YOUR_ACTUAL_STRONG_PASSWORD');
define('APP_KEY', 'GENERATE_A_SECURE_RANDOM_KEY_HERE');
define('DEVICE_KEY', 'GENERATE_A_DIFFERENT_SECURE_KEY_HERE');
Generate secure keys:
openssl rand -hex 32
2.7 Configure Nginx
Copy the nginx configuration:
sudo cp /var/www/otp.intaleqapp.com/nginx.conf /etc/nginx/sites-available/otp.intaleqapp.com
sudo ln -s /etc/nginx/sites-available/otp.intaleqapp.com /etc/nginx/sites-enabled/
Test the configuration:
sudo nginx -t
Reload Nginx:
sudo systemctl reload nginx
2.8 Install SSL Certificate
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d otp.intaleqapp.com
Follow the prompts. Certbot will automatically modify the Nginx config for SSL.
Set up auto-renewal:
sudo crontab -e
# Add: 0 3 * * * certbot renew --quiet --post-hook "systemctl reload nginx"
2.9 Configure PHP-FPM
Ensure PHP-FPM is running:
sudo systemctl enable php8.1-fpm
sudo systemctl start php8.1-fpm
Check the socket path matches your nginx config:
ls -la /var/run/php/php8.1-fpm.sock
2.10 Verify the Backend
Test the health endpoint:
curl https://otp.intaleqapp.com/health
# Should return: {"status":"ok"}
Test the OTP request (will fail without valid app_key, but confirms routing works):
curl -X POST https://otp.intaleqapp.com/api/request-otp \
-H "Content-Type: application/json" \
-d '{"phone":"+962790000000","app_key":"wrong_key"}'
# Should return: {"success":false,"message":"invalid_app_key"}
3. Android Caller App Setup
3.1 Build the App
- Open Android Studio
- Select "Open an existing project"
- Navigate to the
caller-app/directory - Wait for Gradle sync to complete
- Connect your Android device (with USB debugging enabled)
- Click Run ▶️
3.2 First-Time Setup on the Device
- Install the APK on the cheap Android phone with SIM card
- Grant all permissions when prompted:
- Phone (CALL_PHONE)
- Phone State (READ_PHONE_STATE)
- SMS (SEND_SMS)
- Disable Battery Optimization — the app will prompt you; select "Don't optimize" for Flash OTP Caller
- On the main screen:
- Device ID: Auto-generated UUID (note this down)
- Phone Number: Enter the phone number of the SIM in this device (e.g., +96279XXXXXXX)
- App Key: Enter the DEVICE_KEY from your server's config.php
- SIM Slot: Select 0 (or 1 for dual SIM)
- Tap "Register Device" — you should see a success message
- Tap "Start Service" — the notification should show "Flash OTP Caller — Active"
3.3 Verify Service is Running
- Check the persistent notification in the status bar
- The status indicator should show "Service Running ✅"
- The live log area should start showing "Polling..." messages
- Verify on the server that the device is registered:
mysql -u otp_user -p otp_db -e "SELECT * FROM caller_devices;"
3.4 Keep the App Running
- Lock the app in recent apps (prevent it from being killed)
- On Xiaomi/OPPO/Vivo: Enable "Auto-start" in security settings
- On Samsung: Disable "Put app to sleep" in battery settings
- Keep the phone plugged into charger if possible
- Use a dedicated SIM card with an active plan that can make calls and send SMS
4. Flutter Receiver App Setup
4.1 Build the App
cd receiver-app/
flutter pub get
flutter run
For APK build:
flutter build apk --release
For iOS (macOS only):
cd receiver-app/
flutter pub get
cd ios/
pod install
cd ..
flutter run
4.2 Testing on Android
- Install the app on a test phone (Phone B)
- Open the app
- Enter the phone number of Phone B (with +962 prefix)
- Ensure "Android" is selected as device type
- Tap "Send OTP"
- Phone A (Caller device) should call Phone B within 3-6 seconds
- The call will come from a number like +96279XX{OTP4digits}
- Phone B's call log will detect the missed call
- The app extracts the last 4 digits as OTP
- Auto-verifies with the backend
- Success screen appears
4.3 Testing on iOS
- Install the app on an iPhone
- Open the app
- Enter the phone number of the iPhone (with +962 prefix)
- Toggle device type to "iOS"
- Tap "Send OTP"
- The Caller Android device will send an SMS
- iOS keyboard will show the AutoFill suggestion
- Tap the suggestion (one tap only)
- The app auto-submits the OTP
- Success screen appears
5. End-to-End Testing
Complete Test Flow
- Server: Confirm health endpoint returns
{"status":"ok"} - Caller Device: Ensure service is running with "Service Running ✅"
- Receiver App: Enter phone number and tap "Send OTP"
- Timing: Call should arrive within 3-6 seconds
- Auto-detect: Receiver app should detect the missed call within 1-2 seconds
- Verification: Success screen should appear without any user input
- Database check: Verify the record in
otp_requestshasverified_atset
Testing Individual API Endpoints
# Request OTP
curl -X POST https://otp.intaleqapp.com/api/request-otp \
-H "Content-Type: application/json" \
-d '{"phone":"+962790000001","app_key":"YOUR_APP_KEY","device_type":"android"}'
# Verify OTP
curl -X POST https://otp.intaleqapp.com/api/verify-otp \
-H "Content-Type: application/json" \
-d '{"phone":"+962790000001","otp":"1234","app_key":"YOUR_APP_KEY"}'
# Check pending calls (Caller App)
curl "https://otp.intaleqapp.com/api/pending-call?device_id=DEVICE_XXX&app_key=YOUR_DEVICE_KEY"
# Check pending SMS (Caller App)
curl "https://otp.intaleqapp.com/api/pending-sms?device_id=DEVICE_XXX&app_key=YOUR_DEVICE_KEY"
# Register device
curl -X POST https://otp.intaleqapp.com/api/register-device \
-H "Content-Type: application/json" \
-d '{"device_id":"DEVICE_001","phone_number":"+962790000000","sim_slot":0,"app_key":"YOUR_DEVICE_KEY"}'
Monitoring
Check server logs:
# Nginx access log
sudo tail -f /var/log/nginx/otp.intaleqapp.com.access.log
# PHP error log
sudo tail -f /var/log/php8.1-fpm/error.log
# Application log
tail -f /var/www/otp.intaleqapp.com/logs/api.log
Check Redis:
redis-cli
> KEYS otp:*
> TTL otp:+962790000001
> GET otp:+962790000001
Check MySQL:
-- Recent OTP requests
SELECT * FROM otp_requests ORDER BY created_at DESC LIMIT 10;
-- Active devices
SELECT * FROM caller_devices WHERE is_active = 1;
-- Today's stats
SELECT status, COUNT(*) as count
FROM otp_requests
WHERE DATE(created_at) = CURDATE()
GROUP BY status;
6. Troubleshooting
Caller Device Not Receiving Tasks
- Check the device is registered:
SELECT * FROM caller_devices WHERE device_id = 'YOUR_DEVICE_ID'; - Check
is_active = 1andlast_seenis recent - Check the Caller App has internet access
- Check the app_key matches DEVICE_KEY in config.php
- Restart the CallerService
Calls Not Going Through
- Verify CALL_PHONE permission is granted
- Check the SIM card has credit/plan for making calls
- Check the phone number format in the task
- Try manually dialing the number from the phone app
- Check if Do Not Disturb mode is off
SMS Not Sending
- Verify SEND_SMS permission is granted
- Check the SIM card can send SMS
- Check the destination number format
- Test sending a manual SMS from the device
OTP Auto-detect Not Working (Flutter App)
- Verify READ_CALL_LOG and READ_PHONE_STATE permissions are granted
- Check the phone actually received a missed call
- Verify the call type is detected as "missed" (not "rejected")
- If the user answers the call, it won't be in the missed call log
- Try the manual entry fallback
iOS AutoFill Not Working
- Ensure the SMS format includes
@otp.intaleqapp.com #{OTP}at the end - The domain must match the app's associated domain
- Test with a real device (simulators don't receive SMS)
- Check that
AutofillHints.oneTimeCodeis set on the text field - The SMS must come from a phone number, not an alphanumeric sender
Rate Limiting Issues
- Check Redis is running:
redis-cli ping - Clear rate limit for a phone:
redis-cli DEL rate_limit:otp:+962790000001 - Adjust limits in config.php if needed
Database Connection Errors
- Verify MySQL is running:
sudo systemctl status mysql - Check credentials in config.php
- Test connection:
mysql -u otp_user -p -h localhost otp_db - Check PHP-FPM error log
High Server Load
- Check the polling interval (3 seconds default)
- Consider increasing to 5 seconds if many devices are connected
- Use Redis for session data, MySQL for persistent storage only
- Monitor with
top,htop, ormysqladmin processlist