← Back to Backend Docs

eKYC 3.0 — Backend Architecture

.NET 8 / ASP.NET Core Clean Architecture · PostgreSQL · Hangfire · 60+ external providers
Projects: Api · Application · Domain · Infrastructure · Shared

Table of Contents

  1. Tech Stack & Overview
  2. Component Diagram
  3. Clean Architecture Layers
  4. Request Pipeline
  5. Component Sequence Diagrams
  6. Provider Chain Pattern
  7. Mock Mode Strategy
  8. Database Schema Overview
  9. Hangfire & Background Jobs

1 Tech Stack & Overview

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.

LayerChoice
Runtime.NET 8.0 / ASP.NET Core Web API
DatabasePostgreSQL 14+ via EF Core 8 (snake_case naming)
CacheIn-memory (no Redis despite StackExchange.Redis NuGet being present)
AuthJWT Bearer (Issuer / Audience / SecretKey)
LoggingSerilog with CorrelationId enrichment
Background jobsHangfire (PostgreSQL storage) + HostedService (ActivityLogWriterService)
Downstream eventsKafka (Confluent.Kafka 2.13)
API docsSwashbuckle / Swagger at /swagger
PDF generationiText7 8.0.5 (AOF, KRA documents)
ResiliencePolly 8.6.6 (retry, circuit breaker)
ValidationFluentValidation 11.3.1

2 Component Diagram

The big picture: how requests flow from a Flutter/web client through the ASP.NET API into services, providers, and the database.

Flutter Client Android / iOS / Web JWT bearer auth HTTPS MO.Ekyc.Api (ASP.NET Core 8) Middleware Pipeline CorrelationId → Exception → RequestLogging → CORS → JWT Auth → SessionRefresh Filters StageValidationFilter, AdminAuthorizationFilter Controllers Stage0-15, Common, Admin, WarRoom (~33 files) Swagger · Hangfire Dashboard /swagger · /hangfire · ~150 REST endpoints under /api/v1/ MO.Ekyc.Application (CQRS models, interfaces) Commands • Results • Interfaces (IJourneyTracker, ICacheService, IHashingService, IOtpStore, IPaymentService, etc.) MO.Ekyc.Infrastructure Services (~45) Stage-specific: RegistrationService, BankVerification... Common: JourneyTracking, Fraud, Hashing, Otp, NameMatch, BankAutoVerify, Handoff, Verifier External Providers (~60) PAN: Zintlr, NSDL, Hyperverge, UTI, CVL, NDML Bank: Decentro, Hyperverge RPD, SETU, Karza... eSign, Liveness, Face Match, Payment, SMS, Email Persistence (EF Core) EkycDbContext · 75 DbSets · Configurations/ Common Infrastructure ProviderChainExecutor, CircuitBreaker, ApiAuditLogger Hangfire + Downstream ActivityLogWriterService (HostedService) · Hangfire Server · Kafka publisher (DownstreamEventPublisher) MO.Ekyc.Shared ApiRoutes.cs ErrorCodes.cs StageDefinitions.cs ApiResponse<T> JourneyStatus MaskingHelper (no dependencies) MO.Ekyc.Domain 75 entities Lead, Session, BankAccount, PanVerification, + 70 others (pure POCOs) PostgreSQL 14+ ekyc schema, 75 tables Hangfire jobs & locks via EF Core / Npgsql External Provider Ecosystem Zintlr, Hyperverge, NSDL, UTI, CVL KRA, NDML, Decentro, SETU, Karza, YesBank, AINXT (DigiLocker / Face / Liveness / Signature / NameMatch / Geocoding / BankOCR), Digio, eMudhra, Csafe, OneMoney, Razorpay, EPay, Netcore (SMS/Email), TheChecker, Karza email, Zoho CRM, Firebase Dynamic Links, Saathi Short URL, OpenCage Kafka downstream events (CLEVERTAP, CDP, DATALAKE, ZOHO_CRM)

3 Clean Architecture Layers

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.

MO.Ekyc.Api (Presentation) MO.Ekyc.Infrastructure MO.Ekyc.Application Domain 75 entities no deps MO.Ekyc.Shared constants, helpers, ApiResponse<T> Outermost: Controllers, Filters, Middleware Infrastructure: DB, external providers, Hangfire Application: Commands, Results, interfaces Domain: POCO entities only used by all
ProjectDepends onContains
MO.Ekyc.DomainNothing75 POCO entities (Lead, PanVerification, BankAccount, etc.)
MO.Ekyc.SharedNothingApiRoutes, ErrorCodes, StageDefinitions, ApiResponse, helpers
MO.Ekyc.ApplicationDomain, SharedCommands, Results, Interfaces (IJourneyTracker, ICacheService, IHashingService, IOtpStore, IPaymentService)
MO.Ekyc.InfrastructureApplication, Domain, SharedEkycDbContext, EntityTypeConfigurations, 45+ services, 60+ external providers, Hangfire jobs, Kafka publisher
MO.Ekyc.ApiApplication, Infrastructure, SharedControllers, Filters, Middleware, Program.cs (DI composition root)
Dependency Inversion

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.

4 Request Pipeline

Every HTTP request flows through this same sequence of middleware + filters before reaching a controller action.

HTTP Request from Flutter CorrelationId Middleware Adds X-Correlation-Id Exception Handler Middleware Catches + returns 500 Request Logging Serilog sink JWT Auth AuthenticationMW Validates bearer Session Refresh Middleware Sliding expiry CORS Origin check StageValidationFilter [RequiresStage(N)] 409 if stage < N AdminAuthorizationFilter [RequiresRole("ADMIN")] 403 if role missing Controller Action RegistrationController.Signup() Service RegistrationService.SignupAsync() → DbContext, providers

5 Component Sequence Diagrams

5.1 Controller → Service → DbContext → Db

The generic backend call pattern every endpoint follows.

«client» Flutter «controller» Controller «filter» StageValidation «service» StageService «efcore» EkycDbContext «db» PostgreSQL 1. POST + JWT + leadId 2. action invoked 3. check Lead.CurrentStage 4. pass-through 5. LINQ query / Add() 6. SQL INSERT/UPDATE/SELECT 7. rows / affected 8. entity / result 9. Result object 10. ApiResponse<T> JSON

5.2 Service → ProviderChainExecutor → External API (with fallback)

«service» PanVerificationSvc «executor» ProviderChainExec «db» ProviderConfig «provider» ZintlrProvider «provider» NSDLProvider «audit» ApiAuditLogger 1. ExecuteAsync(op) 2. load enabled providers 3. [Zintlr, NSDL, ...] ordered by priority 4. try Zintlr.VerifyPan() 5. HTTP timeout / error 6. fail (open circuit) 7. log Zintlr failure (audit + health) 8. try NSDL.VerifyPan() (fallback) 9. success 10. log NSDL success 11. TResult from NSDL

6 Provider Chain Pattern

Every external integration (PAN, Bank, Face Match, eSign, Liveness, etc.) uses the same pattern: multiple providers, one interface, DB-driven config, automatic fallback.

CategoryInterfaceProviders (priority order)
PANIPanProviderZintlr → Hyperverge → NSDL → UTI → CVL KRA → NDML
BankIBankVerificationProviderHyperverge RPD → Karza → SETU → YesBank → AINXT OCR → Digitap
Face MatchIFaceMatchProviderAINXT → Hyperverge → Digio
SignatureISignatureValidationProviderAINXT
LivenessILivenessProviderAINXT
eSignIEsignProviderHyperverge → eMudhra → NSDL (via EsignVendorRouter)
GeocodingIGeocodingProviderAINXT → OpenCage
Name MatchINameMatchProviderAINXT
PaymentIPaymentGatewayEPay → Razorpay

How it works

  1. ProviderChainExecutor<T> reads enabled providers from provider_configurations table ordered by priority
  2. For each provider: checks circuit breaker state (skip if open)
  3. If provider's mock_mode = true in DB, returns mock response and logs audit
  4. Executes the operation with provider-specific timeout
  5. On success: logs success to ApiAuditLog, updates health metrics, returns result
  6. On failure: records failure in circuit breaker, logs to audit, falls through to next provider
  7. If all providers exhausted: throws AllProvidersFailedException

7 Mock Mode Strategy

Mock 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.

Two levels of mock control

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).

Services that read GlobalMockEnabled

SMS and Email OTPs are separately controlled by Sms.Mode: FIXED_OTP and Email.Mode: FIXED_OTP with FixedOtp: "1234".

8 Database Schema Overview

PostgreSQL 14+ database named ekyc3 with a single schema ekyc. 75 tables total managed via EF Core 8 + EFCore.NamingConventions (automatic snake_case).

Table categories

CategoryCountExamples
Lead & Session3leads, lead_sessions, lead_state_transitions
Verification stages13otp_verifications, pan_verifications, aadhaar_verifications, bank_verification_attempts, liveness_verifications, signature_verifications
Account data6bank_accounts, personal_details, nominees, nominee_witnesses, fatca_declarations, guardians
Documents & eSign5document_uploads, aof_documents, esign_transactions, sebi_submissions
Journey tracking7journey_stage_events, journey_activity_logs, cs_journey_holds, analytics_snapshots, downstream_events
Admin & config13admin_users, provider_configurations, provider_health_metrics, feature_flags, api_audit_log, verifier_reviews
Reference masters20state_masters, city_masters, bank_ifsc_masters, pincode_masters, client_masters, branch_masters, subbroker_masters, kra_records
Account Aggregator & Income2account_aggregator_requests, income_proof_records
Bank Auto Verify (Decentro)1bank_auto_verifies

Sync tables from data lake

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.

9 Hangfire & Background Jobs

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();

Background workers