The backend is a single ASP.NET Core 8.0 Web API application following Clean Architecture principles. It exposes ~150 REST endpoints organized by journey stage (0-15), admin portal, and WarRoom dashboard.
| Layer | Choice |
|---|---|
| Runtime | .NET 8.0 / ASP.NET Core Web API |
| Database | PostgreSQL 14+ via EF Core 8 (snake_case naming) |
| Cache | In-memory (no Redis despite StackExchange.Redis NuGet being present) |
| Auth | JWT Bearer (Issuer / Audience / SecretKey) |
| Logging | Serilog with CorrelationId enrichment |
| Background jobs | Hangfire (PostgreSQL storage) + HostedService (ActivityLogWriterService) |
| Downstream events | Kafka (Confluent.Kafka 2.13) |
| API docs | Swashbuckle / Swagger at /swagger |
| PDF generation | iText7 8.0.5 (AOF, KRA documents) |
| Resilience | Polly 8.6.6 (retry, circuit breaker) |
| Validation | FluentValidation 11.3.1 |
The big picture: how requests flow from a Flutter/web client through the ASP.NET API into services, providers, and the database.
Five projects arranged according to Clean Architecture. Dependencies flow inward: Api depends on Application and Infrastructure; both depend on Domain; Shared is a leaf with no dependencies.
| Project | Depends on | Contains |
|---|---|---|
MO.Ekyc.Domain | Nothing | 75 POCO entities (Lead, PanVerification, BankAccount, etc.) |
MO.Ekyc.Shared | Nothing | ApiRoutes, ErrorCodes, StageDefinitions, ApiResponse, helpers |
MO.Ekyc.Application | Domain, Shared | Commands, Results, Interfaces (IJourneyTracker, ICacheService, IHashingService, IOtpStore, IPaymentService) |
MO.Ekyc.Infrastructure | Application, Domain, Shared | EkycDbContext, EntityTypeConfigurations, 45+ services, 60+ external providers, Hangfire jobs, Kafka publisher |
MO.Ekyc.Api | Application, Infrastructure, Shared | Controllers, Filters, Middleware, Program.cs (DI composition root) |
Business logic in Application defines interfaces (ICacheService, IHashingService, IPaymentService). Infrastructure provides implementations. Services depend only on abstractions. This lets Infrastructure be swapped without touching Application code.
Every HTTP request flows through this same sequence of middleware + filters before reaching a controller action.
The generic backend call pattern every endpoint follows.
Every external integration (PAN, Bank, Face Match, eSign, Liveness, etc.) uses the same pattern: multiple providers, one interface, DB-driven config, automatic fallback.
| Category | Interface | Providers (priority order) |
|---|---|---|
| PAN | IPanProvider | Zintlr → Hyperverge → NSDL → UTI → CVL KRA → NDML |
| Bank | IBankVerificationProvider | Hyperverge RPD → Karza → SETU → YesBank → AINXT OCR → Digitap |
| Face Match | IFaceMatchProvider | AINXT → Hyperverge → Digio |
| Signature | ISignatureValidationProvider | AINXT |
| Liveness | ILivenessProvider | AINXT |
| eSign | IEsignProvider | Hyperverge → eMudhra → NSDL (via EsignVendorRouter) |
| Geocoding | IGeocodingProvider | AINXT → OpenCage |
| Name Match | INameMatchProvider | AINXT |
| Payment | IPaymentGateway | EPay → Razorpay |
ProviderChainExecutor<T> reads enabled providers from provider_configurations table ordered by prioritymock_mode = true in DB, returns mock response and logs auditAllProvidersFailedExceptionMock mode is the development safety valve. With a single flag in appsettings.Development.json, all external provider calls bypass real APIs and return pre-defined mock responses.
Global: MockMode.GlobalMockEnabled in appsettings (affects 13+ services).
Per-provider: provider_configurations.mock_mode boolean in DB (affects one provider at a time via ProviderChainExecutor).
BankAutoVerifyService - Decentro mobile-to-bank returns fake HDFC savingsPanVerificationService - PAN returns mock success with fake nameAadhaarVerificationService - DigiLocker returns mock redirect URLBankVerificationService - RPD returns mock redirect URLLivenessService - Returns mock success with high scoreSignatureService - Returns mock validation passDocumentGenerationService - Returns mock PDF URLEsignService - Returns mock eSign redirect URLAccountCreationService - Returns mock clientIdFundTransferService - Returns test Razorpay keyFraudCheckService - C-SAFE always passesBackgroundCheckService - Zintlr / CSafe all skipSMS and Email OTPs are separately controlled by Sms.Mode: FIXED_OTP and Email.Mode: FIXED_OTP with FixedOtp: "1234".
PostgreSQL 14+ database named ekyc3 with a single schema ekyc. 75 tables total managed via EF Core 8 + EFCore.NamingConventions (automatic snake_case).
| Category | Count | Examples |
|---|---|---|
| Lead & Session | 3 | leads, lead_sessions, lead_state_transitions |
| Verification stages | 13 | otp_verifications, pan_verifications, aadhaar_verifications, bank_verification_attempts, liveness_verifications, signature_verifications |
| Account data | 6 | bank_accounts, personal_details, nominees, nominee_witnesses, fatca_declarations, guardians |
| Documents & eSign | 5 | document_uploads, aof_documents, esign_transactions, sebi_submissions |
| Journey tracking | 7 | journey_stage_events, journey_activity_logs, cs_journey_holds, analytics_snapshots, downstream_events |
| Admin & config | 13 | admin_users, provider_configurations, provider_health_metrics, feature_flags, api_audit_log, verifier_reviews |
| Reference masters | 20 | state_masters, city_masters, bank_ifsc_masters, pincode_masters, client_masters, branch_masters, subbroker_masters, kra_records |
| Account Aggregator & Income | 2 | account_aggregator_requests, income_proof_records |
| Bank Auto Verify (Decentro) | 1 | bank_auto_verifies |
Several reference master tables are expected to be populated by syncing from the existing MOSL data lake / legacy DB, not manually maintained. The full list and sync strategy is documented in the BRD Analysis folder:
Typical sync tables: client_master, branch_master, subbroker_master, employee_pan_master, franchise_pan_whitelist (daily sync); negative_list (from SEBI), bank_ifsc_masters (from RBI). See Database page for details.
Hangfire 1.8 provides durable background job processing backed by PostgreSQL. Dashboard available at /hangfire during development.
builder.Services.AddHangfire(config =>
config
.SetDataCompatibilityLevel(CompatibilityLevel.Version_180)
.UseSimpleAssemblyNameTypeSerializer()
.UseRecommendedSerializerSettings()
.UsePostgreSqlStorage(opts =>
opts.UseNpgsqlConnection(
builder.Configuration.GetConnectionString("EkycDb"))));
builder.Services.AddHangfireServer();
ActivityLogWriterService (HostedService) - reads from async JourneyActivityChannel, batches writes to journey_activity_logs