02 - Mock Mode
Mock mode is the reason you can run the entire 16-stage eKYC journey on your laptop without real SMS, NSDL calls, or bank verification. Understanding it is critical for local development.
The Flutter app has no mock flag. Mock mode lives entirely in the backend's appsettings.Development.json. When enabled, the backend returns mock data (fake URLs, fixed OTPs, simulated provider responses) and the Flutter app just reacts to what the backend sends.
Backend Setup
Mock mode is controlled by a single flag in the backend's Development settings file.
backend/src/MO.Ekyc.Api/appsettings.Development.jsonJSON
{
"Logging": {
"LogLevel": {
"Default": "Debug",
"Microsoft.AspNetCore": "Information",
"Microsoft.EntityFrameworkCore.Database.Command": "Information"
}
},
"ConnectionStrings": {
"EkycDb": "Host=127.0.0.1;Port=5432;Database=ekyc3;Username=postgres;Password=root123;..."
},
"MockMode": {
"GlobalMockEnabled": true
},
"Payment": {
"Razorpay": {
"ClientApiKey": "rzp_test_placeholder"
}
},
"Sms": {
"Mode": "FIXED_OTP",
"FixedOtp": "1234"
},
"Email": {
"Mode": "FIXED_OTP",
"FixedOtp": "1234"
}
}
1. GlobalMockEnabled - affects all backend services. When true, they skip external provider calls and return mock data.
2. Sms.Mode: FIXED_OTP - uses 1234 for all mobile OTPs.
3. Email.Mode: FIXED_OTP - uses 1234 for all email OTPs.
Where the Backend Reads This Flag
At least 13 backend services check GlobalMockEnabled and change behavior accordingly.
| Service | Stage | What it mocks |
|---|---|---|
BankAutoVerifyService.cs | 2 (post-OTP) | Decentro mobile-to-bank: returns mock HDFC active savings |
PanVerificationService.cs | 4 | NSDL PAN verify: returns mock success with fake name |
AadhaarVerificationService.cs | 5 | DigiLocker: returns mock redirect URL (example.com/mock-digilocker) |
BankVerificationService.cs | 6 | Hyperverge RPD: returns mock redirect URL |
LivenessService.cs | 7 | HyperVerge liveness: skips SDK call, returns success |
SignatureService.cs | 8 | AINxt signature validation: bypassed |
DocumentGenerationService.cs | 12 | AOF PDF generation: returns mock PDF URL |
EsignService.cs | 13 | eMudhra/HyperVerge eSign: returns mock redirect URL |
AccountCreationService.cs | 14 | CBOS account creation: returns mock clientId |
FundTransferService.cs | 15 | Razorpay: returns test key + mock redirect |
BackgroundCheckService.cs | 2 (async) | Zintlr + C-SAFE: skipped |
FraudCheckService.cs | 0 | IP velocity, device fingerprint: always pass |
WarRoomService.cs | Admin | Provider health: returns mock metrics |
backend/src/MO.Ekyc.Api/appsettings.Development.jsonbackend/src/MO.Ekyc.Infrastructure/Services/Common/BankAutoVerifyService.csbackend/src/MO.Ekyc.Infrastructure/Services/Stage2/OtpVerificationService.csbackend/src/MO.Ekyc.Infrastructure/ExternalProviders/Common/ProviderChainExecutor.cs- Backend docs (pending) - placeholder
Per-Provider Mock Mode
Beyond the global switch, each external provider has its own mock_mode flag in the provider_configurations database table. This lets you force one provider live while keeping others mocked.
SQLToggle a single provider to live
-- See current provider mock settings
SELECT provider_name, category, mock_mode
FROM ekyc.provider_configurations
ORDER BY category, priority;
-- Turn off mock for HyperVerge liveness (go live for this one provider)
UPDATE ekyc.provider_configurations
SET mock_mode = false
WHERE provider_name = 'HypervergeLiveness';
The backend's ProviderChainExecutor reads this per request - no restart needed.
Flutter Side: Mock URL Detection
When the backend is in mock mode, it returns fake redirect URLs containing strings like example.com, mock-digilocker, or hyperverge.co. The Flutter app detects these URLs and skips opening the WebView or UPI app, calling the success callback directly instead.
Critically, this detection is only active in dev builds. In prod builds, ApiConfig.isDev is a compile-time constant false, so the mock-bypass code is dead and tree-shaken out of the binary.
flutter_app/lib/screens/stage5_digilocker.dartDart~line 120
if (_redirectUrl != null && _redirectUrl!.isNotEmpty) {
// Dev only: skip WebView for mock URLs from backend
if (ApiConfig.isDev &&
(_redirectUrl!.contains('mock-digilocker') ||
_redirectUrl!.contains('example.com'))) {
await _handleMockDigilocker();
return;
}
_initWebView(_redirectUrl!);
setState(() {
_screenState = _ScreenState.webview;
_isLoading = false;
});
}
flutter_app/lib/screens/stage6_bank.dartDart~line 180
// Dev only: skip external UPI launch for mock URLs from backend
if (ApiConfig.isDev &&
(redirectUrl.contains('mock') ||
redirectUrl.contains('example.com') ||
redirectUrl.contains('hyperverge.co') ||
redirectUrl.isEmpty)) {
// Mock: skip UPI app but still show verified state for income selection
final mockResult = await api.completeReversePenny(
transactionRef: transactionId,
accountNumber: '',
ifscCode: '',
);
...
}
What Behaves Differently in Mock Mode
| Stage | Live behavior | Mock behavior |
|---|---|---|
| 2 (OTP) | Real SMS with 6-digit OTP | OTP is always 1234 |
| 3 (Email) | Real SMTP email with OTP | OTP is always 1234 |
| 4 (PAN) | NSDL verify call | Returns mock success |
| 5 (DigiLocker) | Real DigiLocker WebView | WebView skipped, auto-success |
| 6 (Bank) | UPI Reverse Penny Drop via UPI app | UPI launch skipped, mock bank details returned. If Decentro mobile-to-bank found account, user goes directly to verified screen. |
| 7 (Liveness) | HyperVerge camera + face match | Returns success with mock score |
| 8 (Signature) | AINxt signature validation | Accepts any non-empty signature |
| 12 (Doc gen) | Real iText7 PDF generation | Returns mock PDF URL |
| 13 (eSign) | eMudhra / HyperVerge WebView | WebView skipped, auto-signed |
| 14 (Account) | CBOS API creates real account | Returns mock clientId |
| 15 (Fund) | Razorpay live checkout | Razorpay test key, no real charge |
To see the full stage-by-stage flow for both modes, open the Old vs New UI Comparison or check the Journey Flow Bar on the WarRoom dashboard.
Testing Journey Variations
Test a specific provider live
Keep GlobalMockEnabled: true, but update the DB for the one provider you want live:
SQLForce HyperVerge live
UPDATE ekyc.provider_configurations
SET mock_mode = false
WHERE provider_name = 'HypervergeLiveness';
Test full live mode locally
Set GlobalMockEnabled: false and provide real API keys in appsettings.Development.json. This is risky - you'll hit real SMS, real NSDL, real providers. Only do this if you have non-production credentials.
Test Decentro auto-verify
Decentro mobile-to-bank is triggered at Stage 2 (OTP verified). In mock mode, it stores a fake HDFC savings account for the lead. When the user reaches Stage 6, the backend auto-promotes this to a verified bank account and the user lands directly on the Bank Verified screen. Clicking "Change bank" takes them to manual entry.
- Trigger:
backend/src/MO.Ekyc.Infrastructure/Services/Stage2/OtpVerificationService.cs(TriggerBankAutoVerifyIfFirstTime) - Service:
backend/src/MO.Ekyc.Infrastructure/Services/Common/BankAutoVerifyService.cs - Auto-promote:
backend/src/MO.Ekyc.Infrastructure/Services/Stage6/BankVerificationService.cs(GetBankDetailsAsync)
Troubleshooting
OTP 1234 is rejected
- Check
appsettings.Development.json:Sms.Modemust be"FIXED_OTP"(not"REAL") - Restart the backend after changing appsettings
- Make sure you are running in Development environment:
dotnet runuses Development by default
Real DigiLocker WebView opens in mock mode
- Backend returning a real URL means
GlobalMockEnabledisfalse- check the file - Or the backend never set
AadhaarVerificationService._globalMockEnabled- verify the service readsconfig.GetValue<bool>("MockMode:GlobalMockEnabled")
Stage 6 shows hardcoded test bank in prod
The mock URL bypass is guarded by ApiConfig.isDev, which is a compile-time constant. In a prod build (--dart-define=ENV=prod), isDev is always false and the bypass code is dead. If you see mock bank data in prod, either the wrong build flag was used, or the guard was removed. Check env config.
Backend started but app still says "No internet"
- Check CORS in
backend/src/MO.Ekyc.Api/appsettings.json-AllowedOriginsmust includehttp://localhost:8080 - Check the browser Network tab (F12) to see the actual failed request and status code