Design an Insurance Marketplace (Policybazaar-Style)
Building a full-stack insurance comparison and purchase platform serving millions of customers across multiple insurers
Table of Contents
- Introduction — The Insurance Marketplace Vision
- Insurance Industry Landscape
- Functional and Non-Functional Requirements
- Capacity Estimation and Back-of-Envelope
- Data Model and Storage Schema
- High-Level Architecture
- API Design
- Plan Comparison Engine
- Quotation and Pricing Engine
- Lead Management System
- Customer Onboarding and KYC
- Policy Purchase Flow
- Payment Processing and Settlement
- Claims Filing and Tracking
- Renewal Management
- Agent and Broker Portal
- Multi-Insurer Integration Layer
- Recommendation Engine
- Document Management System
- Notification and Communication System
- Fraud Detection and Prevention
- Regulatory Compliance
- Analytics Dashboard
- Cost Estimation
- Testing Strategy
- Interview Q&A
1. Introduction — The Insurance Marketplace Vision
Insurance marketplaces like Policybazaar, Policygenius, and Bajaj Finserv have fundamentally transformed how consumers discover, compare, and purchase insurance products. Policybazaar alone processes over 1.5 million quote requests per day across motor, health, life, and travel insurance categories. The platform aggregates offerings from more than 50 insurance partners, enabling customers to compare premiums, coverage limits, claim settlement ratios, and rider options side by side before making an informed purchase decision. This is not a simple e-commerce website with a product catalog. An insurance marketplace is a deeply regulated, highly transactional, multi-party platform that must handle real-time underwriting, KYC verification, premium calculations, policy document generation, claims processing, and ongoing renewal management.
Building such a platform requires solving complex distributed systems problems: integrating with dozens of insurer APIs that each have different protocols, data formats, and response times; maintaining regulatory compliance across multiple jurisdictions; building trust through transparent comparisons without biasing toward higher-commission products; and handling the full lifecycle of an insurance policy from quote to claim settlement. The system must support millions of concurrent users during peak shopping seasons, process thousands of policy purchases per minute during year-end renewal periods, and maintain 99.95% uptime because any downtime directly translates to lost premium revenue for the platform.
The core challenges of building an insurance marketplace include real-time plan aggregation from heterogeneous insurer APIs, dynamic pricing engines that account for risk factors and regulatory filings, a lead management system that routes customer intent to the right sales agent or digital funnel, document generation and digital policy issuance, and a claims management system that coordinates between the customer, the platform, and the insurer. Each of these subsystems is independently complex, and together they form one of the most challenging system design problems in the fintech domain.
2. Insurance Industry Landscape
The global insurance industry is valued at over 6 trillion dollars annually, with the digital insurance segment growing at a compound annual growth rate of 12 percent. In India alone, the insurance penetration is approximately 4.2 percent of GDP, significantly lower than the global average of 7 percent, representing a massive untapped market opportunity. Online insurance distribution channels account for roughly 15 percent of total insurance sales, but this share is growing rapidly as digital-native consumers prefer self-service research and purchase experiences.
Insurance products are broadly categorized into life insurance, health insurance, motor insurance, travel insurance, property insurance, and specialty lines. Each category has its own regulatory framework, underwriting logic, and distribution model. Life and health insurance are typically underwritten based on the applicant's age, health status, occupation, and lifestyle factors. Motor insurance premiums depend on vehicle type, engine capacity, geographical zone, and claim history. Travel insurance is relatively simpler, priced based on trip duration, destination, and traveler age.
| Insurance Category | Key Risk Factors | Regulatory Body (India) | Typical Commission |
|---|---|---|---|
| Health Insurance | Age, pre-existing conditions, BMI, smoking status | IRDAI | 15-40% of first-year premium |
| Motor Insurance | Vehicle type, zone, engine CC, NCB | IRDAI | 15-17.5% of premium |
| Term Life Insurance | Age, income, health, smoking, family history | IRDAI | 25-40% of first-year premium |
| Travel Insurance | Trip duration, destination, traveler age | IRDAI | 20-35% of premium |
| Home Insurance | Property value, location, construction type | IRDAI | 10-20% of premium |
| Term Insurance | Sum assured, policy term, health class | IRDAI | 30-45% FY, 5-10% renewal |
The competitive landscape of insurance marketplaces includes aggregators like Policybazaar that act as pure comparison platforms, embedded insurance providers like Acko and Digit that underwrite their own products, broker platforms like Bajaj Finserv that distribute products from multiple insurers, and bancassurance channels where banks distribute insurance products through their branch networks. The aggregator model is the most complex from a systems perspective because it requires real-time integration with multiple insurer APIs, while the insurtech model requires building an underwriting engine and claims management system from scratch.
Regulatory compliance is a critical concern. In India, the Insurance Regulatory and Development Authority (IRDAI) mandates that insurance agents and brokers hold valid licenses, that all policy wordings be filed and approved, that commission structures be disclosed, and that a free-look period of 15 to 30 days be provided for life and health insurance products. The platform must also comply with the Insurance Act of 1938, the IRDAI (Registration of Corporate Agents) Regulations, and data localization requirements under the Personal Data Protection Act.
3. Functional and Non-Functional Requirements
Functional Requirements
- Plan Search and Filtering: Users can search for insurance plans across categories (health, motor, life, travel) and filter by premium range, coverage amount, insurer rating, claim settlement ratio, and specific features like cashless hospitals or no-claim bonus.
- Real-Time Quote Generation: The system must fetch real-time premium quotes from multiple insurers based on user-provided parameters (age, sum insured, vehicle details, trip details) and display them within 3 seconds.
- Plan Comparison: Side-by-side comparison of up to 5 insurance plans across 20+ parameters including premium, deductible, sub-limits, waiting periods, and exclusions.
- Policy Purchase: End-to-end online purchase flow including premium payment, KYC document upload, medical declaration, and instant policy issuance.
- Claims Filing: Customers can file claims online, upload supporting documents, track claim status in real-time, and communicate with claim adjusters.
- Renewal Management: Automated renewal reminders via email, SMS, and push notifications 30, 15, 7, and 3 days before policy expiry, with one-click renewal.
- Agent Portal: Licensed agents can manage their customer portfolio, generate quotes, initiate purchases, and track commission earnings.
- Document Management: Digital storage of policy documents, KYC documents, claim documents, and medical reports with secure access controls.
- Recommendation Engine: Personalized plan recommendations based on user demographics, health profile, existing coverage, and purchase history.
- Admin Dashboard: Internal tools for managing insurer integrations, monitoring platform health, reviewing flagged transactions, and generating regulatory reports.
Non-Functional Requirements
| Requirement | Target | Rationale |
|---|---|---|
| Availability | 99.95% | Downtime directly impacts premium revenue and trust |
| Quote Latency (P95) | < 3 seconds | Users abandon after 5 seconds of waiting |
| Purchase Throughput | 1,000 policies/min | Peak during year-end renewal season |
| Data Consistency | Strong for financials | Premium and payment data must be ACID compliant |
| Document Storage | PB scale | Policy documents, KYC images, claim evidence |
| Concurrent Users | 500,000+ | Peak traffic during marketing campaigns |
| PII Encryption | AES-256 at rest | Regulatory mandate for personal and medical data |
4. Capacity Estimation and Back-of-Envelope
Let us estimate the capacity requirements for a Policybazaar-scale insurance marketplace operating in a single large market like India.
Traffic Estimates
- Daily active users: 5 million
- Daily quote requests: 1.5 million (30% of DAU)
- Daily policy purchases: 25,000 (1.7% conversion rate)
- Monthly active users: 30 million
- Peak QPS (marketing campaign): 5,000 quote requests per second
- Average QPS: 17 quote requests per second
Storage Estimates
- User profiles: 50 million users x 2 KB = 100 GB
- Policy records: 20 million policies x 5 KB = 100 GB
- Policy documents (PDF): 20 million x 500 KB = 10 TB
- KYC documents (images): 50 million x 2 MB = 100 TB
- Claim documents: 5 million x 1 MB = 5 TB
- Quote history (logs): 500 million x 1 KB = 500 GB
- Total estimated storage: ~120 TB (growing 30% YoY)
Bandwidth Estimates
- Incoming: 5,000 QPS x 10 KB average = 50 MB/s
- Outgoing (quotes): 5,000 QPS x 50 KB average = 250 MB/s
- Document downloads: 10,000 requests/min x 500 KB = 8.3 MB/s
- Total bandwidth: ~310 MB/s or 25 Gbps
5. Data Model and Storage Schema
The data model for an insurance marketplace is significantly more complex than a typical e-commerce platform because of the multi-party nature of transactions, regulatory requirements for data retention, and the lifecycle complexity of insurance policies. We need to model users, insurers, insurance products, quotes, applications, policies, claims, payments, and documents as core entities, along with their relationships and audit trails.
Core Entities
public class User
{
public Guid UserId { get; set; }
public string Email { get; set; }
public string PhoneNumber { get; set; }
public string FullName { get; set; }
public DateTime DateOfBirth { get; set; }
public string Gender { get; set; }
public string PanNumber { get; set; } // Encrypted at rest
public string AadhaarHash { get; set; } // Hashed, never stored raw
public UserKYCStatus KYCStatus { get; set; }
public UserProfile Profile { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
}
public class InsuranceProduct
{
public Guid ProductId { get; set; }
public Guid InsurerId { get; set; }
public InsuranceCategory Category { get; set; } // Health, Motor, Life, Travel
public string ProductName { get; set; }
public string ProductCode { get; set; }
public string Description { get; set; }
public decimal MinSumInsured { get; set; }
public decimal MaxSumInsured { get; set; }
public int MinAge { get; set; }
public int MaxAge { get; set; }
public decimal CommissionRate { get; set; }
public List<CoverageDetail> Coverages { get; set; }
public List<ExclusionDetail> Exclusions { get; set; }
public List<RiderOption> AvailableRiders { get; set; }
public ClaimSettlementRatio ClaimSettlementRatio { get; set; }
public ProductStatus Status { get; set; }
}
public class Quote
{
public Guid QuoteId { get; set; }
public Guid UserId { get; set; }
public Guid ProductId { get; set; }
public string InsuranceCategory { get; set; }
public Dictionary<string, string> RiskParameters { get; set; }
public decimal AnnualPremium { get; set; }
public decimal MonthlyPremium { get; set; }
public decimal SumInsured { get; set; }
public decimal Deductible { get; set; }
public List<SelectedRider> SelectedRiders { get; set; }
public DateTime QuoteDate { get; set; }
public DateTime ExpiryDate { get; set; }
public QuoteStatus Status { get; set; }
public string InsurerQuoteReference { get; set; }
}
public class Policy
{
public Guid PolicyId { get; set; }
public Guid UserId { get; set; }
public Guid ProductId { get; set; }
public Guid QuoteId { get; set; }
public string PolicyNumber { get; set; } // Issued by insurer
public string PlatformPolicyId { get; set; } // Internal reference
public InsuranceCategory Category { get; set; }
public decimal SumInsured { get; set; }
public decimal AnnualPremium { get; set; }
public decimal PlatformCommission { get; set; }
public DateTime PolicyStartDate { get; set; }
public DateTime PolicyEndDate { get; set; }
public PolicyStatus Status { get; set; }
public string InsurerPolicyDocumentUrl { get; set; }
public List<PolicyEndorsement> Endorsements { get; set; }
public RenewalInfo RenewalInfo { get; set; }
public DateTime CreatedAt { get; set; }
}
public class Claim
{
public Guid ClaimId { get; set; }
public Guid PolicyId { get; set; }
public Guid UserId { get; set; }
public string ClaimNumber { get; set; }
public ClaimType ClaimType { get; set; }
public decimal ClaimedAmount { get; set; }
public decimal? ApprovedAmount { get; set; }
public ClaimStatus Status { get; set; }
public DateTime DateOfIncident { get; set; }
public DateTime DateOfFiling { get; set; }
public string Description { get; set; }
public List<ClaimDocument> Documents { get; set; }
public List<ClaimStatusHistory> StatusHistory { get; set; }
public string InsurerClaimReference { get; set; }
}
public class Payment
{
public Guid PaymentId { get; set; }
public Guid PolicyId { get; set; }
public Guid UserId { get; set; }
public decimal Amount { get; set; }
public string Currency { get; set; } = "INR";
public PaymentMethod Method { get; set; }
public string GatewayTransactionId { get; set; }
public string PlatformTransactionRef { get; set; }
public PaymentStatus Status { get; set; }
public DateTime PaymentDate { get; set; }
public DateTime? RefundDate { get; set; }
public string FailureReason { get; set; }
}
public enum InsuranceCategory
{
Health,
MotorTwoWheeler,
MotorFourWheeler,
TermLife,
WholeLife,
Travel,
Home,
Commercial
}
public enum PolicyStatus
{
QuoteGenerated,
ApplicationSubmitted,
UnderReview,
KYCPending,
PaymentPending,
PaymentCompleted,
PolicyIssued,
Active,
Lapsed,
Cancelled,
ClaimInProgress,
Expired
}
public enum ClaimStatus
{
Filed,
UnderReview,
DocumentsRequested,
AssessmentInProgress,
Approved,
PartiallyApproved,
Rejected,
Settled,
Closed,
AppealInProgress
}
Database Strategy
We employ a polyglot persistence strategy. PostgreSQL serves as the primary relational database for transactional data (users, policies, payments, claims) where ACID guarantees are essential. MongoDB stores quote history, product catalogs, and flexible risk parameter data where schema flexibility is valuable. Redis provides caching for hot data like active quotes, user sessions, and real-time plan comparisons. Amazon S3 with lifecycle policies handles document storage (policy PDFs, KYC documents, claim evidence). Elasticsearch powers the search and filtering experience for plan discovery. Kafka underpins event sourcing for policy state transitions and cross-service communication.
6. High-Level Architecture
The architecture follows a microservices pattern where each bounded context (quotes, purchases, claims, renewals, documents) is an independent service with its own database. The Multi-Insurer Adapter is the most architecturally significant component because it abstracts the heterogeneity of insurer APIs behind a unified interface. Each insurer integration is implemented as a separate adapter class that handles protocol translation (SOAP to REST), data mapping (insurer-specific field names to canonical models), retry logic, circuit breaking, and response caching. The adapter pattern ensures that adding a new insurer integration does not require changes to the core quote and purchase services.
Event-driven architecture using Apache Kafka connects the services asynchronously. When a policy is purchased, a PolicyIssued event is published that triggers downstream processes: the notification service sends the policy document to the customer, the renewal service creates a renewal reminder schedule, the commission service calculates agent earnings, and the analytics service updates conversion funnels. This decoupling ensures that the purchase flow completes quickly while background processes handle non-critical side effects.
7. API Design
The API layer follows RESTful conventions with versioned endpoints, JWT-based authentication, and comprehensive input validation. All endpoints require authentication except plan browsing and quote generation. Rate limiting is applied per user (100 requests/minute) and per IP (1000 requests/minute) to prevent abuse.
Core API Endpoints
| Method | Endpoint | Description | Auth |
|---|---|---|---|
| GET | /api/v1/plans | Browse plans with filters | No |
| GET | /api/v1/plans/{productId} | Get plan details | No |
| POST | /api/v1/quotes/generate | Generate quotes from multiple insurers | Yes |
| GET | /api/v1/quotes/{quoteId} | Get quote details | Yes |
| POST | /api/v1/applications | Submit insurance application | Yes |
| POST | /api/v1/policies/purchase | Purchase a policy | Yes |
| GET | /api/v1/policies | List user policies | Yes |
| GET | /api/v1/policies/{policyId} | Get policy details | Yes |
| POST | /api/v1/claims | File a new claim | Yes |
| GET | /api/v1/claims/{claimId} | Get claim status | Yes |
| POST | /api/v1/claims/{claimId}/documents | Upload claim documents | Yes |
| POST | /api/v1/renewals/{policyId}/renew | Renew a policy | Yes |
| POST | /api/v1/kyc/upload | Upload KYC documents | Yes |
| GET | /api/v1/payments/{paymentId}/status | Check payment status | Yes |
| POST | /api/v1/recommendations | Get personalized recommendations | Yes |
C# API Controller
[ApiController]
[Route("api/v1/quotes")]
[Authorize]
public class QuoteController : ControllerBase
{
private readonly IQuoteService _quoteService;
private readonly IInsurerAdapter _insurerAdapter;
private readonly ILogger<QuoteController> _logger;
public QuoteController(
IQuoteService quoteService,
IInsurerAdapter insurerAdapter,
ILogger<QuoteController> logger)
{
_quoteService = quoteService;
_insurerAdapter = insurerAdapter;
_logger = logger;
}
[HttpPost("generate")]
[RateLimit(PerMinute = 20)]
public async Task<ActionResult<QuoteResponse>> GenerateQuotes(
[FromBody] QuoteRequest request)
{
var validationResult = await _quoteService.ValidateRequestAsync(request);
if (!validationResult.IsValid)
return BadRequest(new { errors = validationResult.Errors });
var userId = GetUserIdFromClaims();
_logger.LogInformation(
"Quote request from user {UserId} for {Category} with params {@Params}",
userId, request.Category, request.RiskParameters);
var eligibleProducts = await _quoteService
.GetEligibleProductsAsync(request.Category, request.RiskParameters);
var quoteTasks = eligibleProducts.Select(async product =>
{
try
{
var quote = await _insurerAdapter.GetQuoteAsync(
product.InsurerId,
product.ProductCode,
request.RiskParameters,
request.SumInsured);
return quote;
}
catch (InsurerTimeoutException ex)
{
_logger.LogWarning(ex,
"Timeout fetching quote from insurer {InsurerId}", product.InsurerId);
return null;
}
});
var quotes = await Task.WhenAll(quoteTasks);
var validQuotes = quotes.Where(q => q != null).ToList();
await _quoteService.PersistQuoteHistoryAsync(userId, request, validQuotes);
return Ok(new QuoteResponse
{
RequestId = Guid.NewGuid(),
Quotes = validQuotes.OrderBy(q => q.AnnualPremium).ToList(),
TotalInsurers = eligibleProducts.Count,
SuccessfulResponses = validQuotes.Count,
GeneratedAt = DateTimeOffset.UtcNow
});
}
}
8. Plan Comparison Engine
The plan comparison engine is the heart of the insurance marketplace. It must normalize data from dozens of insurers, each using different terminology, coverage structures, and benefit definitions, into a unified comparison framework. A health insurance plan from Insurer A might call its base coverage "Gold Plan" while Insurer B calls the equivalent "Premium Health" — the comparison engine must map these to equivalent coverage tiers using a canonical data model.
Normalization Pipeline
The normalization pipeline operates in three stages. First, raw product data is ingested from insurer APIs or bulk feeds into a staging area. Second, a rules-based mapper normalizes field names, converts benefit limits to common units (for example, converting all sub-limits to absolute rupee amounts), and maps insurer-specific coverage codes to a standard coverage taxonomy. Third, a similarity scoring algorithm identifies equivalent plans across insurers based on coverage overlap, premium range, and feature matching.
public class PlanComparisonEngine
{
private readonly IProductRepository _productRepo;
private readonly ICoverageNormalizer _normalizer;
private readonly IComparisonScorer _scorer;
public async Task<ComparisonResult> ComparePlansAsync(
List<Guid> productIds,
ComparisonContext context)
{
var products = await _productRepo.GetByIdsAsync(productIds);
var normalizedPlans = products.Select(p => _normalizer.Normalize(p)).ToList();
var comparisonMatrix = BuildComparisonMatrix(normalizedPlans, context);
var scores = _scorer.ScorePlans(normalizedPlans, context.UserPreferences);
var rankings = GenerateRankings(normalizedPlans, scores, context.RankingCriteria);
return new ComparisonResult
{
Plans = normalizedPlans,
ComparisonMatrix = comparisonMatrix,
Scores = scores,
Rankings = rankings,
FeatureHighlights = ExtractHighlights(normalizedPlans),
Warnings = DetectConcerns(normalizedPlans)
};
}
private Dictionary<string, ComparisonColumn> BuildComparisonMatrix(
List<NormalizedPlan> plans,
ComparisonContext context)
{
var matrix = new Dictionary<string, ComparisonColumn>();
var standardFeatures = new[]
{
"Sum Insured", "Annual Premium", "Deductible",
"Pre-existing Disease Waiting Period",
"Specific Disease Waiting Period",
"Room Rent Limit", "ICU Charges Limit",
"Day Care Procedures", "AYUSH Treatment",
"No Claim Bonus", "Restoration Benefit",
"Ambulance Cover", "Pre and Post Hospitalization",
"Maternity Cover", "Newborn Cover",
"Organ Donor Cover", "Daily Cash Benefit"
};
foreach (var feature in standardFeatures)
{
matrix[feature] = new ComparisonColumn
{
FeatureName = feature,
Values = plans.Select(p =>
new FeatureValue
{
PlanId = p.ProductId,
Value = p.GetFeatureValue(feature),
IsBestInClass = false,
Highlight = null
}).ToList()
};
}
MarkBestInClass(matrix);
return matrix;
}
}
Comparison Data Model
| Feature Category | Comparison Parameters | Normalization Method |
|---|---|---|
| Financial | Premium, Deductible, Co-pay, Sub-limits | Annualize all premiums, convert to absolute amounts |
| Coverage | Sum insured, Room rent, ICU, Day care | Map to standard taxonomy, normalize units |
| Waiting Periods | Initial, Pre-existing, Specific disease | Convert to months, flag if above industry median |
| Exclusions | Standard exclusions, special exclusions | Tag with severity levels, highlight key exclusions |
| Benefits | AYUSH, Maternity, Restoration, Ambulance | Boolean flag with limits and conditions |
| Insurer Quality | Claim ratio, Solvency ratio, Reviews | Normalize to 1-10 score, include trend data |
9. Quotation and Pricing Engine
The quotation engine is responsible for calculating accurate premium quotes in real-time. Unlike simple e-commerce pricing, insurance pricing is actuarial in nature and depends on multiple risk factors that vary by insurance category, regulatory filings, and the individual's risk profile. The engine must support both synchronous pricing (where the insurer provides pre-computed rates) and asynchronous underwriting (where the insurer evaluates the risk in real-time based on the applicant's details).
Risk Factor Model
public class HealthInsurancePricingEngine
{
private readonly IRateTableService _rateTables;
private readonly ILoadingFactorCalculator _loadingCalculator;
private readonly IDiscountService _discountService;
public async Task<PremiumQuote> CalculatePremiumAsync(
HealthRiskProfile riskProfile,
ProductConfiguration productConfig)
{
// Step 1: Get base rate from rate table
var baseRate = await _rateTables.GetBaseRateAsync(
productConfig.ProductCode,
riskProfile.Age,
riskProfile.CityTier,
riskProfile.SumInsured);
// Step 2: Apply medical loading factors
var loadingFactor = _loadingCalculator.Calculate(riskProfile, new LoadingFactors
{
BmiLoading = CalculateBmiLoading(riskProfile.Height, riskProfile.Weight),
SmokingLoading = riskProfile.IsSmoker ? 1.40m : 1.0m,
PreExistingLoading = CalculatePreExistingLoading(riskProfile.PreExistingConditions),
OccupationLoading = GetOccupationLoading(riskProfile.Occupation),
FamilySizeLoading = GetFamilySizeLoading(riskProfile.Members.Count),
GeographyLoading = GetGeographyLoading(riskProfile.PinCode)
});
// Step 3: Calculate gross premium
var grossPremium = baseRate * loadingFactor;
// Step 4: Apply age-based co-pay if applicable
var coPayFactor = riskProfile.Age >= 60 ? 0.10m : 0m;
var premiumAfterCopay = grossPremium * (1 - coPayFactor);
// Step 5: Apply voluntary deductible discount
var deductibleDiscount = _discountService.GetDeductibleDiscount(
riskProfile.VoluntaryDeductible, productConfig);
// Step 6: Apply no-claim bonus for renewals
var ncbDiscount = riskProfile.ClaimFreeYears > 0
? GetNoClaimBonusDiscount(riskProfile.ClaimFreeYears, productConfig)
: 0m;
var netPremium = premiumAfterCopay * (1 - deductibleDiscount - ncbDiscount);
// Step 7: Apply GST (18% on health insurance premium)
var gstAmount = netPremium * 0.18m;
var finalPremium = netPremium + gstAmount;
return new PremiumQuote
{
BasePremium = baseRate,
LoadingApplied = loadingFactor - 1.0m,
GrossPremium = grossPremium,
DiscountsApplied = deductibleDiscount + ncbDiscount,
NetPremium = netPremium,
GST = gstAmount,
TotalPayablePremium = Math.Round(finalPremium, 2),
MonthlyEquivalent = Math.Round(finalPremium / 12, 2),
CoPayPercentage = coPayFactor * 100,
Breakdown = new PremiumBreakdown
{
BaseRate = baseRate,
MedicalLoading = (loadingFactor - 1.0m) * 100,
DeductibleDiscount = deductibleDiscount * 100,
NCBDiscount = ncbDiscount * 100,
GSTRate = 18
}
};
}
}
10. Lead Management System
Lead management is a critical revenue driver for insurance marketplaces. Not all users convert through the self-service digital funnel — many require agent-assisted sales, especially for complex products like term life insurance or comprehensive health plans with high sum insured amounts. The lead management system captures user intent signals (quote requests, plan comparisons, page views), scores lead quality using a machine learning model, and routes qualified leads to the most suitable agent or sales team based on product expertise, language preference, geographic proximity, and current workload.
Lead Scoring Model
public class LeadScoringService
{
private readonly IMLModelService _mlService;
private readonly ILeadRepository _leadRepo;
public async Task<LeadScore> ScoreLeadAsync(UserLead lead)
{
var features = new LeadFeatures
{
// Behavioral signals
PagesViewed = lead.PageViewHistory.Count,
TimeOnSite = lead.TotalSessionDurationMinutes,
QuoteRequestsMade = lead.QuoteRequestCount,
PlansCompared = lead.PlansComparedCount,
ReturnsToSite = lead.SessionCount,
// Demographic signals
Age = lead.UserProfile.Age,
CityTier = lead.UserProfile.CityTier,
Income = lead.UserProfile.DeclaredIncome,
HasExistingPolicy = lead.ExistingPolicies.Any(),
// Intent signals
ProductCategory = lead.RequestedCategory,
SumInsuredRequested = lead.RequestedSumInsured,
HasDependents = lead.UserProfile.DependentCount > 0,
BrowsingUrgency = CalculateUrgency(lead),
// History signals
PreviousPurchaseHistory = lead.UserProfile.PreviousPurchases.Count,
AgentInteractionHistory = lead.AgentInteractions.Count,
ClaimHistory = lead.UserProfile.PreviousClaims.Count
};
var mlScore = await _mlService.PredictConversionProbabilityAsync(features);
var ruleScore = CalculateRuleBasedScore(lead);
// Weighted combination: 70% ML model, 30% business rules
var finalScore = (mlScore * 0.7m) + (ruleScore * 0.3m);
var priority = MapScoreToPriority(finalScore);
return new LeadScore
{
LeadId = lead.LeadId,
Score = finalScore,
Priority = priority,
RecommendedAgent = await FindBestAgentAsync(lead, priority),
EstimatedConversionProbability = mlScore,
SuggestedIncentive = CalculateIncentive(finalScore, lead.RequestedCategory),
ScoredAt = DateTimeOffset.UtcNow
};
}
}
Lead Routing Rules
| Priority | Score Range | SLA Response Time | Routing Strategy |
|---|---|---|---|
| Platinum | 0.85 - 1.0 | Within 5 minutes | Best available senior agent, phone callback |
| Gold | 0.65 - 0.84 | Within 30 minutes | Available agent with product expertise |
| Silver | 0.40 - 0.64 | Within 2 hours | Digital funnel with email follow-up |
| Bronze | 0.0 - 0.39 | Within 24 hours | Automated email sequence, self-service |
11. Customer Onboarding and KYC
Customer onboarding for insurance is significantly more complex than for e-commerce because of Know Your Customer (KYC) regulatory requirements. In India, IRDAI mandates KYC verification for all life and health insurance policies with sum insured above 50,000 rupees. The KYC process involves identity verification (Aadhaar or PAN card), address verification, and for certain products, medical underwriting that requires health declarations and sometimes medical examinations.
The onboarding flow must be designed to minimize friction while ensuring regulatory compliance. We use a progressive profiling approach where we collect only the information needed at each step, allow users to get quotes without completing KYC (only identity details are required for quotes), and defer full KYC to the point of purchase. Digital KYC through Aadhaar OTP verification or DigiLocker integration can complete identity verification in under 60 seconds, while manual KYC through document upload and review takes 24 to 48 hours.
KYC Integration Architecture
public class KYCVerificationService
{
private readonly IAadhaarGateway _aadhaarGateway;
private readonly IPanVerificationGateway _panGateway;
private readonly IDigiLockerGateway _digiLocker;
private readonly IOcrService _ocrService;
private readonly IKYCRepository _kycRepo;
public async Task<KYCResult> PerformKYCAsync(
KYCRequest request,
CancellationToken cancellationToken)
{
var verificationTasks = new List<Task<VerificationResult>>();
// Aadhaar-based eKYC (preferred, fastest)
if (request.AadhaarNumber != null)
{
verificationTasks.Add(
_aadhaarGateway.PerformEKYCAsync(
request.AadhaarNumber,
request.OtpCode));
}
// PAN verification
if (request.PanNumber != null)
{
verificationTasks.Add(
_panGateway.VerifyPanAsync(
request.PanNumber,
request.FullName,
request.DateOfBirth));
}
// DigiLocker document fetch
if (request.DigiLockerConsent)
{
verificationTasks.Add(
_digiLocker.FetchDocumentsAsync(
request.DigiLockerCode));
}
var results = await Task.WhenAll(verificationTasks);
var kycResult = new KYCResult
{
AadhaarVerified = results.Any(r =>
r.Type == VerificationType.Aadhaar && r.IsSuccess),
PanVerified = results.Any(r =>
r.Type == VerificationType.PAN && r.IsSuccess),
AddressVerified = results.Any(r =>
r.Type == VerificationType.Address && r.IsSuccess),
VerificationLevel = DetermineVerificationLevel(results),
VerifiedAt = DateTimeOffset.UtcNow,
ExpiresAt = DateTimeOffset.UtcNow.AddYears(1)
};
await _kycRepo.SaveKYCRecordAsync(request.UserId, kycResult);
return kycResult;
}
private KYCLevel DetermineVerificationLevel(VerificationResult[] results)
{
var aadhaar = results.FirstOrDefault(r => r.Type == VerificationType.Aadhaar);
var pan = results.FirstOrDefault(r => r.Type == VerificationType.PAN);
if (aadhaar?.IsSuccess == true && pan?.IsSuccess == true)
return KYCLevel.Full;
if (aadhaar?.IsSuccess == true)
return KYCLevel.EKYC;
if (pan?.IsSuccess == true)
return KYCLevel.Minimal;
return KYCLevel.Unverified;
}
}
12. Policy Purchase Flow
The policy purchase flow is the most critical revenue-generating workflow in the system. It must be fast, reliable, and handle edge cases like payment failures, insurer API timeouts, and duplicate purchase attempts gracefully. The purchase flow involves multiple steps: quote selection, proposal form completion, KYC verification, premium payment, insurer API call for policy generation, document delivery, and post-purchase analytics tracking. Each step must be idempotent and recoverable so that if any step fails, the user can resume from the point of failure without starting over.
Purchase Flow State Machine
public class PolicyPurchaseOrchestrator
{
private readonly IPurchaseStateStore _stateStore;
private readonly IQuoteService _quoteService;
private readonly IKYCService _kycService;
private readonly IPaymentService _paymentService;
private readonly IInsurerAdapter _insurerAdapter;
private readonly IDocumentService _documentService;
private readonly IEventPublisher _eventPublisher;
public async Task<PurchaseResult> ProcessPurchaseAsync(
PurchaseRequest request,
CancellationToken cancellationToken)
{
var purchaseId = Guid.NewGuid();
var state = new PurchaseState
{
PurchaseId = purchaseId,
UserId = request.UserId,
QuoteId = request.QuoteId,
Step = PurchaseStep.Initiated,
CreatedAt = DateTimeOffset.UtcNow
};
try
{
// Step 1: Validate quote is still active and premium hasn't changed
state.Step = PurchaseStep.ValidatingQuote;
await _stateStore.SaveAsync(state);
var currentQuote = await _quoteService.GetQuoteAsync(request.QuoteId);
if (currentQuote.Status != QuoteStatus.Active)
return PurchaseResult.Failed("Quote has expired");
if (currentQuote.AnnualPremium != request.ExpectedPremium)
return PurchaseResult.Failed("Premium has changed. Please refresh your quote.");
// Step 2: Verify KYC status
state.Step = PurchaseStep.VerifyingKYC;
await _stateStore.SaveAsync(state);
var kycStatus = await _kycService.GetKYCStatusAsync(request.UserId);
if (!kycStatus.IsVerified)
{
state.KYCRequired = true;
state.Step = PurchaseStep.KYCPending;
await _stateStore.SaveAsync(state);
return PurchaseResult.RequiresKYC(purchaseId);
}
// Step 3: Process payment with idempotency key
state.Step = PurchaseStep.ProcessingPayment;
await _stateStore.SaveAsync(state);
var paymentResult = await _paymentService.ProcessAsync(
new PaymentRequest
{
UserId = request.UserId,
Amount = request.ExpectedPremium,
IdempotencyKey = $"purchase_{purchaseId}",
Method = request.PaymentMethod,
Metadata = new Dictionary<string, string>
{
["purchaseId"] = purchaseId.ToString(),
["quoteId"] = request.QuoteId.ToString()
}
}, cancellationToken);
if (!paymentResult.IsSuccess)
{
state.Step = PurchaseStep.PaymentFailed;
state.PaymentFailureReason = paymentResult.FailureReason;
await _stateStore.SaveAsync(state);
return PurchaseResult.PaymentFailed(paymentResult.FailureReason);
}
// Step 4: Submit application to insurer
state.Step = PurchaseStep.SubmittingToInsurer;
state.PaymentId = paymentResult.PaymentId;
await _stateStore.SaveAsync(state);
var applicationResult = await _insurerAdapter.SubmitApplicationAsync(
currentQuote.InsurerQuoteReference,
request.ProposalForm,
cancellationToken);
if (!applicationResult.IsSuccess)
{
// Initiate payment refund
await _paymentService.InitiateRefundAsync(
paymentResult.PaymentId,
"Insurer rejected application");
state.Step = PurchaseStep.InsurerRejected;
state.InsurerRejectionReason = applicationResult.RejectionReason;
await _stateStore.SaveAsync(state);
return PurchaseResult.InsurerRejected(applicationResult.RejectionReason);
}
// Step 5: Policy issued successfully
state.Step = PurchaseStep.Completed;
state.PolicyNumber = applicationResult.PolicyNumber;
state.PolicyDocumentUrl = applicationResult.DocumentUrl;
await _stateStore.SaveAsync(state);
// Step 6: Publish domain event
await _eventPublisher.PublishAsync(new PolicyIssuedEvent
{
PurchaseId = purchaseId,
PolicyId = applicationResult.PolicyId,
UserId = request.UserId,
ProductId = currentQuote.ProductId,
Premium = request.ExpectedPremium,
PolicyStartDate = applicationResult.PolicyStartDate,
PolicyEndDate = applicationResult.PolicyEndDate
});
return PurchaseResult.Success(
applicationResult.PolicyId,
applicationResult.PolicyNumber,
applicationResult.DocumentUrl);
}
catch (Exception ex)
{
state.Step = PurchaseStep.Error;
state.ErrorMessage = ex.Message;
await _stateStore.SaveAsync(state);
throw;
}
}
}
13. Payment Processing and Settlement
Payment processing for insurance is more complex than standard e-commerce because of the higher transaction amounts, regulatory requirements around premium collection, and the need for precise settlement reconciliation with insurers. Insurance premiums in India can range from a few hundred rupees for basic motor insurance to several lakh rupees for comprehensive health or life insurance policies. The platform must support multiple payment methods including UPI, net banking, credit cards, debit cards, wallets, and EMI options for high-value policies.
Payment Architecture
The payment service operates as a saga that coordinates premium collection, GST calculation, insurer settlement, and platform commission tracking. When a customer pays 12,000 rupees for a health insurance policy, the system must allocate the amount correctly: approximately 9,000 rupees to the insurer's premium pool, approximately 2,000 rupees as platform commission, and approximately 1,000 rupees as GST. The settlement with insurers happens on a T+3 basis where the platform collects all premiums for a billing cycle and remits the insurer share minus commission on the fourth business day.
public class PaymentSettlementService
{
private readonly IPaymentGateway _gateway;
private readonly ICommissionService _commissionService;
private readonly ISettlementRepository _settlementRepo;
public async Task<SettlementResult> SettleTransactionAsync(
PolicyPayment payment,
CancellationToken cancellationToken)
{
var insurerSettlement = new InsurerSettlement
{
TransactionId = payment.TransactionId,
PolicyId = payment.PolicyId,
InsurerId = payment.InsurerId,
GrossPremium = payment.Amount,
PlatformCommission = await _commissionService
.CalculateCommissionAsync(payment),
GSTOnCommission = CalculateGST(
await _commissionService.CalculateCommissionAsync(payment)),
NetRemittance = 0, // Calculated below
SettlementDate = DateTimeOffset.UtcNow.AddDays(3),
Status = SettlementStatus.Pending
};
insurerSettlement.NetRemittance =
insurerSettlement.GrossPremium -
insurerSettlement.PlatformCommission -
insurerSettlement.GSTOnCommission;
await _settlementRepo.SaveAsync(insurerSettlement);
return new SettlementResult
{
InsurerRemittance = insurerSettlement.NetRemittance,
PlatformCommission = insurerSettlement.PlatformCommission,
GSTCollected = insurerSettlement.GSTOnCommission,
SettlementDate = insurerSettlement.SettlementDate
};
}
}
Payment Method Support Matrix
| Payment Method | Min Amount | Max Amount | Settlement T+X | Failure Rate |
|---|---|---|---|---|
| UPI | ₹1 | ₹1,00,000 | T+1 | 2-3% |
| Credit Card | ₹100 | ₹5,00,000 | T+2 | 1-2% |
| Debit Card | ₹100 | ₹2,00,000 | T+2 | 3-5% |
| Net Banking | ₹100 | ₹10,00,000 | T+1 | 4-6% |
| EMI | ₹5,000 | ₹5,00,000 | T+3 | 1-3% |
| Wallet | ₹1 | ₹50,000 | T+1 | 1-2% |
14. Claims Filing and Tracking
The claims experience is where insurance truly delivers on its promise. A poorly managed claims process destroys customer trust and damages the platform's reputation. The claims management system must provide a seamless filing experience, transparent status tracking, efficient document collection, and timely resolution. For cashless claims, the platform must coordinate between the customer, the hospital or service provider, and the insurer in real-time. For reimbursement claims, the platform must manage document verification, claim assessment, and settlement tracking.
public class ClaimsProcessingEngine
{
private readonly IClaimRepository _claimRepo;
private readonly IDocumentVerifier _docVerifier;
private readonly IInsurerClaimsGateway _insurerGateway;
private readonly IClaimAssessor _assessor;
private readonly IEventPublisher _events;
public async Task<ClaimResult> ProcessClaimAsync(
ClaimSubmission submission,
CancellationToken cancellationToken)
{
var claim = new Claim
{
ClaimId = Guid.NewGuid(),
PolicyId = submission.PolicyId,
UserId = submission.UserId,
ClaimType = submission.ClaimType,
ClaimedAmount = submission.ClaimedAmount,
DateOfIncident = submission.IncidentDate,
Status = ClaimStatus.Filed,
FiledAt = DateTimeOffset.UtcNow
};
// Step 1: Validate policy is active and covers the incident
var policy = await ValidatePolicyCoverageAsync(
submission.PolicyId, submission.ClaimType, submission.IncidentDate);
if (!policy.IsValid)
return ClaimResult.Rejected("Policy does not cover this incident");
// Step 2: Initial document verification
var docVerification = await _docVerifier.VerifyDocumentsAsync(
submission.Documents, submission.ClaimType);
if (!docVerification.AllDocumentsValid)
{
claim.Status = ClaimStatus.DocumentsRequested;
claim.MissingDocuments = docVerification.MissingDocuments;
await _claimRepo.SaveAsync(claim);
return ClaimResult.DocumentsRequired(docVerification.MissingDocuments);
}
// Step 3: Pre-assessment eligibility check
var eligibility = await _assessor.CheckEligibilityAsync(
claim, policy, submission);
if (!eligibility.IsEligible)
{
claim.Status = ClaimStatus.Rejected;
claim.RejectionReason = eligibility.Reason;
await _claimRepo.SaveAsync(claim);
return ClaimResult.Rejected(eligibility.Reason);
}
// Step 4: Submit to insurer for assessment
claim.Status = ClaimStatus.AssessmentInProgress;
claim.InsurerClaimReference = await _insurerGateway.SubmitClaimAsync(
claim, submission.Documents);
await _claimRepo.SaveAsync(claim);
// Step 5: Track insurer assessment
await _events.PublishAsync(new ClaimFiledEvent
{
ClaimId = claim.ClaimId,
PolicyId = claim.PolicyId,
UserId = claim.UserId,
ClaimedAmount = claim.ClaimedAmount,
FiledAt = claim.FiledAt
});
return ClaimResult.Accepted(claim.ClaimId, claim.InsurerClaimReference);
}
}
Claims SLA Targets
| Claim Type | Document Verification | Assessment | Settlement | Total SLA |
|---|---|---|---|---|
| Cashless Hospitalization | Real-time | 2-4 hours | At discharge | Same day |
| Reimbursement (Health) | 1 business day | 3-5 business days | 2-3 business days | 10 business days |
| Motor Accidental | 2 business days | 5-7 business days | 3-5 business days | 15 business days |
| Term Life Death Claim | 3 business days | 10-15 business days | 5-7 business days | 30 business days |
| Travel Insurance | 1 business day | 3-5 business days | 5-7 business days | 15 business days |
15. Renewal Management
Renewal management represents one of the most significant revenue streams for insurance marketplaces. In India, approximately 60 percent of health insurance policies lapse at renewal because customers either forget or find the renewal process cumbersome. A well-designed renewal system can improve retention rates from 40 percent to 75 percent, directly translating to recurring commission revenue. The system must track renewal dates for millions of policies, send timely reminders through multiple channels, handle premium changes due to age-based loading or regulatory rate revisions, and provide a frictionless one-click renewal experience.
public class RenewalManagementService
{
private readonly IPolicyRepository _policyRepo;
private readonly INotificationService _notificationService;
private readonly IPaymentService _paymentService;
private readonly IInsurerAdapter _insurerAdapter;
public async Task ProcessDailyRenewalsAsync()
{
var policiesDueForReminder = await _policyRepo
.GetPoliciesDueForRenewalAsync(
reminderDays: new[] { 30, 15, 7, 3, 1 });
foreach (var policy in policiesDueForReminder)
{
var daysUntilExpiry = (policy.PolicyEndDate - DateTime.UtcNow).Days;
var renewalQuote = await _insurerAdapter.GetRenewalQuoteAsync(
policy.InsurerId, policy.PolicyNumber);
var premiumChange = renewalQuote.AnnualPremium - policy.AnnualPremium;
var premiumChangePercent = (premiumChange / policy.AnnualPremium) * 100;
var reminder = new RenewalReminder
{
PolicyId = policy.PolicyId,
UserId = policy.UserId,
DaysUntilExpiry = daysUntilExpiry,
CurrentPremium = policy.AnnualPremium,
RenewalPremium = renewalQuote.AnnualPremium,
PremiumChange = premiumChange,
PremiumChangePercent = premiumChangePercent,
ReminderType = DetermineReminderType(daysUntilExpiry),
RenewalUrl = GenerateRenewalLink(policy)
};
await _notificationService.SendRenewalReminderAsync(reminder);
// Auto-renew eligible policies with stored payment method
if (policy.AutoRenewEnabled && daysUntilExpiry == 7)
{
await ProcessAutoRenewalAsync(policy, renewalQuote);
}
}
}
private async Task ProcessAutoRenewalAsync(
Policy policy, RenewalQuote quote)
{
try
{
var paymentResult = await _paymentService.ChargeAsync(
policy.UserId,
quote.AnnualPremium,
policy.StoredPaymentMethodId,
$"Auto-renewal for policy {policy.PolicyNumber}");
if (paymentResult.IsSuccess)
{
await _insurerAdapter.RenewPolicyAsync(
policy.InsurerId, policy.PolicyNumber);
await _policyRepo.ExtendPolicyAsync(
policy.PolicyId, quote.AnnualPremium);
}
}
catch (Exception ex)
{
// Fall back to manual renewal reminder
await _notificationService.SendUrgentRenewalReminderAsync(
policy.UserId, policy.PolicyId);
}
}
}
Renewal Reminder Schedule
| Days Before Expiry | Channel | Content Type | Action Required |
|---|---|---|---|
| 30 days | Renewal preview with premium comparison | Inform | |
| 15 days | Email + SMS | Detailed renewal with new benefits | Encourage renewal |
| 7 days | Email + SMS + Push | Urgent renewal with auto-renewal trigger | Renew or set auto-renew |
| 3 days | All channels + Phone call | Critical expiry warning | Immediate action |
| 1 day | All channels + Agent callback | Last chance warning | Critical: renew now |
| Post expiry (1 day) | Lapse notification with revival options | Revival information |
16. Agent and Broker Portal
The agent and broker portal provides licensed insurance agents with tools to manage their customer portfolio, generate quotes, initiate policy purchases, track commissions, and manage renewals. In India, insurance agents are regulated by IRDAI and must hold valid agency licenses. The platform must ensure that agents can only sell products they are licensed for, that all agent-customer interactions are logged for audit purposes, and that commission calculations are transparent and accurate.
public class AgentPortalService
{
private readonly IAgentRepository _agentRepo;
private readonly ICommissionCalculator _commissionCalc;
private readonly IQuoteService _quoteService;
public async Task<AgentDashboard> GetDashboardAsync(Guid agentId)
{
var agent = await _agentRepo.GetByIdAsync(agentId);
var portfolio = await _agentRepo.GetCustomerPortfolioAsync(agentId);
var pendingRenewals = await _agentRepo.GetPendingRenewalsAsync(agentId);
var commissionReport = await _commissionCalc
.CalculateCommissionReportAsync(agentId, DateTimeOffset.UtcNow.AddMonths(-1));
return new AgentDashboard
{
AgentName = agent.FullName,
LicenseNumber = agent.IRDAILicenseNumber,
LicensedProducts = agent.LicensedProductCategories,
Portfolio = new AgentPortfolio
{
TotalCustomers = portfolio.Count,
ActivePolicies = portfolio.Count(p => p.HasActivePolicy),
PoliciesExpiringThisMonth = pendingRenewals.Count,
MonthlyPremiumCollection = portfolio.Sum(p => p.CurrentPremium),
YearToDateCommission = commissionReport.TotalCommission,
PendingCommission = commissionReport.PendingPayout
},
TopCustomers = portfolio
.OrderByDescending(c => c.CurrentPremium)
.Take(10)
.Select(c => new AgentCustomerSummary
{
CustomerName = c.CustomerName,
ActivePolicies = c.ActivePolicies.Count,
TotalPremium = c.CurrentPremium,
NextRenewal = c.NextRenewalDate,
RiskScore: c.RiskScore
}).ToList(),
ComplianceStatus = new AgentComplianceStatus
{
LicenseExpiry = agent.LicenseExpiryDate,
TrainingCompleted = agent.ContinuingEducationHoursThisYear,
MinimumTrainingRequired = 25,
ComplaintsOnRecord = agent.ComplaintsCount
}
};
}
}
Agent Commission Structure
| Product Category | First Year Commission | Renewal Commission | Bonus Structure |
|---|---|---|---|
| Term Life Insurance | 25-40% | 5-10% | ₹5,000 per 1 Cr policy |
| Health Insurance | 15-30% | 10-15% | 2% bonus above ₹50L annual |
| Motor Comprehensive | 12.5-17.5% | 12.5-17.5% | Volume bonus at 500+ policies |
| Motor Third Party | 12.5% | 12.5% | Standard rate |
| Travel Insurance | 20-35% | N/A | Seasonal bonus campaigns |
17. Multi-Insurer Integration Layer
The multi-insurer integration layer is the most technically complex component of the insurance marketplace. Each insurer has its own API protocol (REST, SOAP, or proprietary XML over HTTP), data format (JSON, XML, EDI), authentication mechanism (API keys, OAuth 2.0, mutual TLS), and response semantics. Some insurers provide real-time APIs, while others require batch file uploads. The integration layer must abstract all this heterogeneity behind a unified interface while handling timeouts, retries, circuit breaking, and data transformation reliably.
public interface IInsurerAdapter
{
Task<List<InsurerQuote>> GetQuotesAsync(
InsuranceCategory category,
Dictionary<string, string> riskParameters,
decimal sumInsured);
Task<ApplicationResult> SubmitApplicationAsync(
string insurerQuoteReference,
ProposalForm proposal);
Task<ClaimSubmissionResult> SubmitClaimAsync(
Claim claim, List<ClaimDocument> documents);
Task<PolicyStatus> CheckPolicyStatusAsync(
string insurerPolicyNumber);
}
public class InsurerAdapterRegistry
{
private readonly Dictionary<Guid, IInsurerAdapter> _adapters;
private readonly Dictionary<Guid, CircuitBreaker> _circuitBreakers;
private readonly ILogger<InsurerAdapterRegistry> _logger;
public async Task<InsurerQuote> GetQuoteWithFallbackAsync(
Guid insurerId,
InsuranceCategory category,
Dictionary<string, string> riskParameters,
decimal sumInsured)
{
var circuitBreaker = _circuitBreakers[insurerId];
if (circuitBreaker.IsOpen)
{
_logger.LogWarning(
"Circuit breaker open for insurer {InsurerId}, skipping", insurerId);
return null;
}
try
{
var adapter = _adapters[insurerId];
var quotes = await adapter.GetQuotesAsync(category, riskParameters, sumInsured);
circuitBreaker.RecordSuccess();
return quotes.FirstOrDefault();
}
catch (Exception ex)
{
circuitBreaker.RecordFailure();
_logger.LogError(ex,
"Failed to get quote from insurer {InsurerId}", insurerId);
return null;
}
}
}
public class HealthInsurerAdapterA : IInsurerAdapter
{
private readonly HttpClient _httpClient;
private readonly IXmlSerializer _serializer;
private readonly IAuthenticator _authenticator;
public async Task<List<InsurerQuote>> GetQuotesAsync(
InsuranceCategory category,
Dictionary<string, string> riskParameters,
decimal sumInsured)
{
// Translate to insurer-specific request format
var request = new InsurerAQuoteRequest
{
PlanCode = riskParameters["planCode"],
InsuredAge = int.Parse(riskParameters["age"]),
InsuredGender = riskParameters["gender"],
SumInsured = sumInsured,
CityTier = int.Parse(riskParameters["cityTier"]),
SmokingStatus = riskParameters["smokingStatus"],
PreExistingDiseases = riskParameters["ped"]
?.Split(',').ToList() ?? new List<string>()
};
var xmlRequest = _serializer.Serialize(request);
var authToken = await _authenticator.GetTokenAsync("insurerA");
var httpRequest = new HttpRequestMessage(
HttpMethod.Post,
"https://api.insurera.com/quotes/health")
{
Content = new StringContent(xmlRequest, Encoding.UTF8, "application/xml")
};
httpRequest.Headers.Add("Authorization", $"Bearer {authToken}");
httpRequest.Headers.Add("X-Partner-Code", "POLICYBAZAAR");
var response = await _httpClient.SendAsync(httpRequest);
response.EnsureSuccessStatusCode();
var xmlResponse = await response.Content.ReadAsStringAsync();
var insurerResponse = _serializer.Deserialize<InsurerAQuoteResponse>(xmlResponse);
// Translate back to canonical format
return insurerResponse.Quotes.Select(q => new InsurerQuote
{
InsurerId = Guid.Parse("insurerA-guid"),
InsurerName = "Insurer A",
ProductName = q.PlanName,
AnnualPremium = q.PremiumAmount,
SumInsured = q.SumInsured,
Deductible = q.DeductibleAmount,
Features = q.Benefits.ToDictionary(b => b.Name, b => b.Value),
QuoteReference = q.QuoteId,
ValidUntil = q.ExpiryDate
}).ToList();
}
}
18. Recommendation Engine
The recommendation engine drives cross-sell and upsell revenue by suggesting relevant insurance products based on the user's current coverage, life events, demographics, and behavioral patterns. A 30-year-old who just purchased term life insurance might be recommended a health insurance plan. A customer with a newborn might be shown child education plans and health insurance with maternity benefits. A user who recently turned 45 might see plans with critical illness riders, as the statistical risk of critical illness increases significantly after 45.
public class InsuranceRecommendationEngine
{
private readonly IUserProfileService _profileService;
private readonly IProductCatalogService _catalogService;
private readonly ICollaborativeFilterService _collaborativeFilter;
private readonly IContentBasedFilterService _contentFilter;
public async Task<List<ProductRecommendation>> GetRecommendationsAsync(
Guid userId,
int maxRecommendations = 10)
{
var profile = await _profileService.GetFullProfileAsync(userId);
var existingCoverage = profile.ExistingPolicies;
// Rule-based recommendations (deterministic, explainable)
var ruleBased = GetRuleBasedRecommendations(profile, existingCoverage);
// Collaborative filtering (users like you also bought)
var collaborative = await _collaborativeFilter
.GetSimilarUserRecommendationsAsync(userId, 20);
// Content-based filtering (based on your risk profile)
var contentBased = await _contentFilter
.GetProfileMatchRecommendationsAsync(profile, 20);
// Life event triggered recommendations
var lifeEventBased = GetLifeEventRecommendations(profile);
// Score and rank all recommendations
var allCandidates = ruleBased
.Concat(collaborative)
.Concat(contentBased)
.Concat(lifeEventBased)
.GroupBy(r => r.ProductId)
.Select(g => new
{
ProductId = g.Key,
Scores = g.Select(r => r.Score).ToList(),
Reasons = g.Select(r => r.Reason).Distinct().ToList()
})
.Select(g => new ProductRecommendation
{
ProductId = g.ProductId,
FinalScore = CombineScores(g.Scores),
Reasons = g.Reasons,
RecommendationType = DeterminePrimaryType(g.Reasons)
})
.OrderByDescending(r => r.FinalScore)
.Take(maxRecommendations)
.ToList();
return allCandidates;
}
private List<ProductRecommendation> GetRuleBasedRecommendations(
UserProfile profile,
List<PolicyInfo> existingCoverage)
{
var recommendations = new List<ProductRecommendation>();
// No health insurance but has dependents
if (!existingCoverage.Any(p => p.Category == InsuranceCategory.Health)
&& profile.DependentCount > 0)
{
recommendations.Add(new ProductRecommendation
{
Score = 0.95m,
Reason = "Critical gap: No health insurance with dependents",
RecommendationType = GapFill
});
}
// Health coverage too low for age
var healthPolicy = existingCoverage
.FirstOrDefault(p => p.Category == InsuranceCategory.Health);
if (healthPolicy != null && profile.Age > 35
&& healthPolicy.SumInsured < 10_00_000)
{
recommendations.Add(new ProductRecommendation
{
Score = 0.85m,
Reason = "Health coverage may be insufficient for your age",
RecommendationType = Upsell
});
}
// No term life insurance with income and dependents
if (!existingCoverage.Any(p => p.Category == InsuranceCategory.TermLife)
&& profile.AnnualIncome > 5_00_000
&& profile.DependentCount > 0)
{
recommendations.Add(new ProductRecommendation
{
Score = 0.90m,
Reason = "Protect your family's financial future",
RecommendationType = GapFill
});
}
return recommendations;
}
}
19. Document Management System
The document management system handles the storage, retrieval, and lifecycle management of millions of insurance-related documents including policy wordings, KYC documents, claim evidence, medical reports, and regulatory filings. Documents must be stored securely with encryption at rest, access controlled based on user roles, and retained for periods mandated by regulation (typically 7 to 10 years after policy expiry for insurance documents in India). The system must support version control for policy documents that get updated during renewals or endorsements.
public class DocumentManagementService
{
private readonly IBlobStorage _blobStorage;
private readonly IDocumentRepository _docRepo;
private readonly IEncryptionService _encryption;
private readonly IOcrService _ocrService;
public async Task<StoredDocument> StoreDocumentAsync(
DocumentUpload upload)
{
// Step 1: Validate file type and size
var validation = ValidateDocument(upload);
if (!validation.IsValid)
throw new DocumentValidationException(validation.Errors);
// Step 2: Encrypt document content
var encryptedContent = await _encryption.EncryptAsync(
upload.Content,
keyVersion: _encryption.CurrentKeyVersion);
// Step 3: Generate storage path with tenant isolation
var storagePath = GenerateStoragePath(
upload.UserId,
upload.DocumentType,
upload.PolicyId);
// Step 4: Upload to blob storage
var blobReference = await _blobStorage.UploadAsync(
storagePath,
encryptedContent,
new BlobMetadata
{
ContentType = upload.ContentType,
FileName = upload.FileName,
DocumentType = upload.DocumentType.ToString(),
RetentionExpiry = CalculateRetentionDate(upload.DocumentType)
});
// Step 5: Run OCR for searchable index (async)
var ocrText = await _ocrService.ExtractTextAsync(upload.Content);
// Step 6: Save document record
var document = new StoredDocument
{
DocumentId = Guid.NewGuid(),
UserId = upload.UserId,
PolicyId = upload.PolicyId,
DocumentType = upload.DocumentType,
OriginalFileName = upload.FileName,
BlobReference = blobReference,
FileSize = upload.Content.Length,
ContentType = upload.ContentType,
OcrText = ocrText,
UploadedAt = DateTimeOffset.UtcNow,
RetentionExpiry = CalculateRetentionDate(upload.DocumentType),
AccessControls = new List<AccessControl>
{
new AccessControl { UserId = upload.UserId, Permission = ReadWrite },
new AccessControl { Role = "ComplianceOfficer", Permission = ReadOnly }
}
};
await _docRepo.SaveAsync(document);
return document;
}
private DateTime CalculateRetentionDate(DocumentType type)
{
return type switch
{
DocumentType.PolicyWording => DateTime.UtcNow.AddYears(10),
DocumentType.KYCDocument => DateTime.UtcNow.AddYears(5),
DocumentType.ClaimDocument => DateTime.UtcNow.AddYears(8),
DocumentType.MedicalReport => DateTime.UtcNow.AddYears(7),
DocumentType.PaymentReceipt => DateTime.UtcNow.AddYears(7),
_ => DateTime.UtcNow.AddYears(5)
};
}
}
Document Type and Retention
| Document Type | Max Size | Accepted Formats | Retention Period | Encryption |
|---|---|---|---|---|
| Policy Wording | 10 MB | 10 years post expiry | AES-256 | |
| KYC Document | 5 MB | JPG, PNG, PDF | 5 years post last policy | AES-256 |
| Claim Evidence | 20 MB | JPG, PNG, PDF | 8 years post settlement | AES-256 |
| Medical Report | 15 MB | 7 years post expiry | AES-256 | |
| Payment Receipt | 2 MB | 7 years | AES-256 | |
| Proposal Form | 5 MB | 10 years post expiry | AES-256 |
20. Notification and Communication System
The notification system must deliver timely, relevant, and compliant communications across multiple channels including email, SMS, push notifications, WhatsApp Business API, and in-app messages. Insurance communications are heavily regulated — IRDAI mandates that certain communications like policy issuance, premium due reminders, claim status updates, and renewal notices must be sent through specific channels with specific content. The notification system must support templating, personalization, scheduling, delivery tracking, and opt-out management.
public class NotificationOrchestrator
{
private readonly ITemplateEngine _templateEngine;
private readonly IEmailProvider _emailProvider;
private readonly ISmsProvider _smsProvider;
private readonly IPushProvider _pushProvider;
private readonly IWhatsAppProvider _whatsAppProvider;
private readonly INotificationPreferenceStore _prefStore;
public async Task SendNotificationAsync(
NotificationRequest request)
{
var preferences = await _prefStore
.GetPreferencesAsync(request.UserId);
var template = await _templateEngine
.RenderAsync(request.TemplateId, request.Data);
var channels = DetermineChannels(request.Type, preferences);
var sendTasks = channels.Select(async channel =>
{
try
{
switch (channel)
{
case NotificationChannel.Email:
await _emailProvider.SendAsync(
new EmailMessage
{
To = preferences.Email,
Subject = template.Subject,
Body = template.EmailBody,
IsTransactional = IsTransactional(request.Type)
});
break;
case NotificationChannel.SMS:
await _smsProvider.SendAsync(
new SmsMessage
{
To = preferences.PhoneNumber,
Body = template.SmsBody
});
break;
case NotificationChannel.Push:
await _pushProvider.SendAsync(
new PushMessage
{
UserId = request.UserId,
Title = template.PushTitle,
Body = template.PushBody,
DeepLink = request.DeepLink
});
break;
case NotificationChannel.WhatsApp:
await _whatsAppProvider.SendAsync(
new WhatsAppMessage
{
To = preferences.PhoneNumber,
TemplateName = request.WhatsAppTemplate,
Parameters = request.Data
});
break;
}
await LogNotificationDelivery(
request, channel, DeliveryStatus.Sent);
}
catch (Exception ex)
{
await LogNotificationDelivery(
request, channel, DeliveryStatus.Failed, ex.Message);
}
});
await Task.WhenAll(sendTasks);
}
}
Notification Types and Compliance
| Notification Type | Required Channels | Timing | Regulatory Requirement |
|---|---|---|---|
| Policy Issuance | Email + SMS | Within 1 hour of purchase | IRDAI mandates immediate acknowledgment |
| Premium Due Reminder | Email + SMS | 30, 15, 7 days before due | Mandatory for life and health insurance |
| Claim Status Update | Email + SMS + Push | Within 24 hours of status change | IRDAI grievance redressal guidelines |
| Renewal Notice | Email + SMS | 45 days before expiry | IRDAI renewal guidelines |
| Free-Look Cancellation | Email + SMS | Within 15 days of issuance | Mandatory free-look period notification |
| KYC Reminder | 7 days before KYC expiry | RBI/IRDAI KYC requirements |
21. Fraud Detection and Prevention
Insurance fraud is a significant concern that costs the industry billions of dollars annually. In the Indian market, common fraud patterns include false claims with fabricated medical records, premium payment fraud using stolen payment credentials, identity fraud using fake KYC documents, and agent fraud where agents sell policies without customer consent to earn commissions. The fraud detection system must operate in real-time for payment fraud and policy purchase anomalies, and batch mode for claims fraud that requires deeper analysis.
public class FraudDetectionEngine
{
private readonly IRuleEngine _ruleEngine;
private readonly IFraudMLModel _mlModel;
private readonly ISuspiciousActivityRepository _suspiciousRepo;
private readonly IDeviceFingerprintService _deviceService;
public async Task<FraudAssessment> AssessTransactionAsync(
TransactionContext context)
{
var assessments = new List<FraudIndicator>();
// Layer 1: Rule-based checks (< 5ms)
var ruleResults = await _ruleEngine.EvaluateAsync(
context.TransactionType,
context.ToFeatureVector());
assessments.AddRange(ruleResults);
// Layer 2: Device fingerprint and behavioral analysis (< 20ms)
var deviceAnalysis = await _deviceService.AnalyzeAsync(
context.DeviceFingerprint,
context.UserId);
if (deviceAnalysis.IsSuspicious)
{
assessments.Add(new FraudIndicator
{
Type = FraudType.DeviceAnomaly,
Score = deviceAnalysis.RiskScore,
Details = deviceAnalysis.Findings
});
}
// Layer 3: ML model prediction (< 100ms)
var mlPrediction = await _mlModel.PredictFraudProbabilityAsync(
context.ToFeatureVector());
assessments.Add(new FraudIndicator
{
Type = FraudType.MLPrediction,
Score = mlPrediction.Probability,
Details = mlPrediction.Explanation
});
// Layer 4: Network analysis for known fraud patterns (< 200ms)
var networkAnalysis = await AnalyzeFraudNetworkAsync(context);
if (networkAnalysis.ConnectedToKnownFraud)
{
assessments.Add(new FraudIndicator
{
Type = FraudType.NetworkLink,
Score = 0.9m,
Details = networkAnalysis.ConnectionDetails
});
}
var overallScore = CalculateOverallRiskScore(assessments);
var decision = MapScoreToDecision(overallScore);
if (decision != FraudDecision.Approved)
{
await _suspiciousRepo.SaveAsync(new SuspiciousActivity
{
Context = context,
Assessments = assessments,
OverallScore = overallScore,
Decision = decision,
FlaggedAt = DateTimeOffset.UtcNow
});
}
return new FraudAssessment
{
Decision = decision,
OverallRiskScore = overallScore,
Indicators = assessments,
RequiresManualReview = decision == FraudDecision.Review,
SuggestedAction = GetSuggestedAction(decision, overallScore)
};
}
}
Fraud Detection Rules
| Rule Category | Example Rule | Risk Score | Action |
|---|---|---|---|
| Velocity | Same user generates 50+ quotes in 10 minutes | 0.7 | Flag for review, temp throttle |
| Device | New device with mismatched geolocation | 0.6 | Require additional authentication |
| Payment | Multiple failed payment attempts from different cards | 0.8 | Block, escalate to fraud team |
| KYC | Aadhaar photo does not match selfie | 0.9 | Reject KYC, require in-person verification |
| Claims | Hospital bill amount statistically improbable | 0.85 | Manual assessment, request additional docs |
| Network | Multiple policies linked to same bank account | 0.75 | Investigate agent, audit all linked policies |
| Timing | Large claim filed within 30 days of policy purchase | 0.7 | Enhanced scrutiny, senior adjuster review |
22. Regulatory Compliance
Insurance is one of the most heavily regulated industries in the world. In India, the IRDAI prescribes detailed regulations on product design, pricing, distribution, claims handling, customer communication, and data management. The platform must comply with the Insurance Act of 1938, IRDAI Act of 1999, IRDAI (Registration of Corporate Agents) Regulations, IRDAI (Protection of Policyholders' Interests) Regulations, and the Personal Data Protection Act. Non-compliance can result in penalties up to 25 crore rupees, license revocation, and criminal prosecution.
Key Compliance Requirements
| Requirement | Regulation | Implementation | Audit Frequency |
|---|---|---|---|
| Agent Licensing Verification | IRDAI (Licensing) Regs | Real-time license validation before agent onboarding | Annual |
| Free-Look Period | IRDAI (POPI) Regs | 15-day cancellation window for life, 30 days for health | Per policy |
| Premium Transparency | Insurance Act 1938 | All-inclusive premium display with GST breakdown | Quarterly |
| Commission Disclosure | IRDAI (Comm) Regs | Platform commission disclosed in policy documents | Annual |
| Data Localization | DPDP Act 2023 | All PII stored in India-based data centers | Continuous |
| Claim Settlement Timeline | IRDAI (POPI) Regs | Claims settled within 30 days of final document submission | Monthly |
| Grievance Redressal | IRDAI (POPI) Regs | Complaint resolution within 15 days, escalation to IRDAI ombudsman | Monthly |
| Anti-Money Laundering | PMLA 2002 | AML screening for high-value policies, suspicious transaction reporting | Per transaction |
public class ComplianceService
{
private readonly IRegulatoryRuleEngine _ruleEngine;
private readonly IAuditLogService _auditLog;
private readonly ILicenseVerificationService _licenseService;
public async Task<ComplianceCheckResult> ValidatePolicyPurchaseAsync(
PolicyPurchaseContext context)
{
var violations = new List<ComplianceViolation>();
// Check 1: Agent license validity
if (context.AgentId.HasValue)
{
var license = await _licenseService
.VerifyLicenseAsync(context.AgentId.Value);
if (!license.IsValid)
{
violations.Add(new ComplianceViolation
{
Code = "LIC-001",
Regulation = "IRDAI (Licensing) Regs",
Description = "Agent license is expired or suspended",
Severity = ViolationSeverity.Critical
});
}
}
// Check 2: Free-look period computation
if (context.Category == InsuranceCategory.TermLife
|| context.Category == InsuranceCategory.Health)
{
var freeLookDays = context.Category == InsuranceCategory.Health ? 30 : 15;
// Ensure free-look terms are included in policy document
if (!context.PolicyDocument.Contains("free-look"))
{
violations.Add(new ComplianceViolation
{
Code = "FL-001",
Regulation = "IRDAI (POPI) Regs",
Description = "Free-look period terms not in policy document",
Severity = ViolationSeverity.High
});
}
}
// Check 3: Premium transparency
var totalPremium = context.BasePremium + context.GST;
if (Math.Abs(context.DisplayedPremium - totalPremium) > 0.01m)
{
violations.Add(new ComplianceViolation
{
Code = "PREM-001",
Regulation = "Insurance Act 1938",
Description = "Displayed premium does not match calculated total",
Severity = ViolationSeverity.Critical
});
}
// Check 4: Commission disclosure
if (context.CommissionRate > 0
&& !context.PolicyDocument.Contains("commission"))
{
violations.Add(new ComplianceViolation
{
Code = "COMM-001",
Regulation = "IRDAI (Commission) Regs",
Description = "Commission disclosure missing from policy document",
Severity = ViolationSeverity.Medium
});
}
await _auditLog.LogComplianceCheckAsync(context, violations);
return new ComplianceCheckResult
{
IsCompliant = !violations.Any(v =>
v.Severity == ViolationSeverity.Critical),
Violations = violations,
CheckedAt = DateTimeOffset.UtcNow
};
}
}
23. Analytics Dashboard
The analytics dashboard provides business stakeholders with real-time visibility into platform performance, conversion funnels, insurer performance metrics, agent productivity, claims ratios, and regulatory KPIs. The dashboard must handle high-cardinality data across multiple dimensions (product category, insurer, geography, agent, time period) and support both real-time operational metrics and historical trend analysis. We use ClickHouse as the analytical database because of its superior columnar storage, real-time ingestion, and sub-second query performance on billions of rows.
Key Metrics Tracked
| Metric Category | Key Metrics | Refresh Rate | Retention |
|---|---|---|---|
| User Acquisition | Daily active users, New registrations, Traffic sources, Bounce rate | Real-time | 2 years |
| Quote Funnel | Quotes generated, Quotes viewed, Plan comparisons, Quote-to-application rate | Real-time | 2 years |
| Purchase Funnel | Applications submitted, KYC completion rate, Payment success rate, Policy issuance rate | Real-time | 2 years |
| Revenue | Gross premium collected, Platform commission, Revenue per user, ARPU | 5 minutes | 5 years |
| Insurer Performance | Quote response time, Policy issuance rate, Claim settlement ratio, Customer rating | Hourly | 3 years |
| Agent Productivity | Policies sold, Premium collected, Conversion rate, Customer satisfaction | Daily | 3 years |
| Claims | Claims filed, Claims settled, Average settlement time, Fraud detection rate | Daily | 5 years |
| Renewals | Renewal rate, Auto-renewal adoption, Renewal premium change, Lapse rate | Daily | 3 years |
public class AnalyticsService
{
private readonly IClickHouseConnection _clickhouse;
private readonly IRealTimeMetricsCollector _metricsCollector;
public async Task<ConversionFunnel> GetConversionFunnelAsync(
DateTime startDate,
DateTime endDate,
FunnelFilters filters)
{
var query = @"
SELECT
countIf(event = 'page_view') as page_views,
countIf(event = 'quote_generated') as quotes_generated,
countIf(event = 'quote_viewed') as quotes_viewed,
countIf(event = 'plan_compared') as plans_compared,
countIf(event = 'application_started') as applications_started,
countIf(event = 'application_submitted') as applications_submitted,
countIf(event = 'kyc_completed') as kyc_completed,
countIf(event = 'payment_initiated') as payments_initiated,
countIf(event = 'payment_completed') as payments_completed,
countIf(event = 'policy_issued') as policies_issued
FROM analytics_events
WHERE date BETWEEN @start AND @end
AND category = @category
AND source = @source";
var result = await _clickhouse.QueryAsync<ConversionFunnel>(
query,
new { start = startDate, end = endDate,
category = filters.Category,
source = filters.Source });
return result;
}
public async Task<List<InsurerPerformanceMetrics>> GetInsurerPerformanceAsync(
DateTime startDate,
DateTime endDate)
{
var query = @"
SELECT
insurer_id,
insurer_name,
count() as total_quotes,
avg(response_time_ms) as avg_response_time,
countIf(status = 'success') * 100.0 / count() as success_rate,
sumIf(premium, status = 'policy_issued') as total_premium_collected,
countIf(status = 'policy_issued') as policies_issued,
sumIf(claim_amount, claim_status = 'settled') /
nullIf(sum(claim_amount), 0) as claim_settlement_ratio
FROM insurer_interactions
WHERE date BETWEEN @start AND @end
GROUP BY insurer_id, insurer_name
ORDER BY total_premium_collected DESC";
return await _clickhouse.QueryAsync<InsurerPerformanceMetrics>(
query, new { start = startDate, end = endDate });
}
}
24. Cost Estimation
Building and operating a Policybazaar-scale insurance marketplace requires significant infrastructure investment. The following cost estimates are for a platform serving 30 million monthly active users, processing 25,000 policies per day, and storing 120 terabytes of data.
Infrastructure Cost Breakdown (Monthly)
| Component | Specification | Monthly Cost (USD) | Notes |
|---|---|---|---|
| Application Servers (EKS) | 20x m5.2xlarge | $5,600 | Auto-scaling, 3 AZ deployment |
| PostgreSQL (RDS) | db.r5.4xlarge Multi-AZ | $3,200 | Primary transactional database |
| MongoDB Atlas | M50 cluster | $2,400 | Product catalog, quote history |
| Redis (ElastiCache) | cache.r5.xlarge cluster | $1,800 | 3-node cluster for caching |
| Amazon S3 | 120 TB stored, 50 TB transfer | $2,800 | Document storage with lifecycle |
| ClickHouse Cloud | 3-node cluster | $4,500 | Analytics and reporting |
| Elasticsearch | 3x r5.large.search | $1,500 | Plan search and filtering |
| MSK (Kafka) | kafka.m5.large x3 | $2,100 | Event streaming |
| CDN (CloudFront) | 50 TB transfer | $4,200 | Global content delivery |
| WAF and Shield | Standard protection | $1,200 | DDoS and bot protection |
| Monitoring (Datadog) | Pro plan | $3,500 | APM, logs, infrastructure |
| KMS and Secrets | Key management | $500 | Encryption key management |
| Data Transfer | 500 GB/month outbound | $1,500 | Cross-AZ and internet |
| Machine Learning (SageMaker) | Inference endpoints | $2,200 | Fraud detection, recommendations |
| SMS and Email | 5M SMS + 10M emails | $3,000 | Notification delivery |
| OCR and KYC APIs | 200K verifications | $2,000 | Aadhaar, PAN verification |
| Backup and DR | Cross-region replication | $1,500 | Disaster recovery |
| Total Monthly Infrastructure | $39,500 | ||
| Engineering Team (15 engineers) | $150,000 | Avg $10K/fte/month loaded | |
| DevOps and SRE (3 engineers) | $36,000 | $12K/fte/month loaded | |
| Compliance and Legal | $15,000 | Regulatory advisory | |
| Grand Total (Monthly) | $240,500 | ||
25. Testing Strategy
Testing an insurance marketplace requires a comprehensive strategy that covers unit testing of pricing algorithms, integration testing with insurer API mocks, end-to-end testing of the purchase flow, performance testing under peak load, and compliance testing to ensure regulatory requirements are met. The pricing engine must be tested against known actuarial tables to ensure premium calculations are accurate within acceptable tolerances. The purchase flow must be tested for idempotency and recovery from failures at each step.
Testing Layers
| Test Layer | Scope | Tools | Coverage Target | Run Frequency |
|---|---|---|---|---|
| Unit Tests | Pricing calculations, data transformations, validators | xUnit, Moq | 90% line coverage | Every commit |
| Integration Tests | Database operations, insurer API mocks, payment gateway | Testcontainers, WireMock | All service boundaries | Every PR |
| Contract Tests | Insurer API contracts, frontend API contracts | Pact | All external integrations | Daily |
| E2E Tests | Complete purchase flow, claims filing, renewal | Playwright, Selenium | Critical user journeys | Nightly |
| Performance Tests | Quote generation, policy purchase, search | k6, Gatling | Peak load scenarios | Weekly |
| Security Tests | OWASP Top 10, PII exposure, payment fraud | OWASP ZAP, SonarQube | All API endpoints | Weekly |
| Compliance Tests | Premium accuracy, free-look periods, commission disclosure | Custom xUnit suite | All regulatory rules | Per release |
public class HealthPremiumCalculatorTests
{
[Fact]
public async Task CalculatePremium_Age30_Nonsmoker_BaseCase_ReturnsCorrectPremium()
{
// Arrange
var rateTable = CreateMockRateTable(
baseRate: 8500m,
age30Factor: 1.0m);
var calculator = new HealthInsurancePricingEngine(
rateTable, new LoadingFactorCalculator(), new DiscountService());
var riskProfile = new HealthRiskProfile
{
Age = 30,
Gender = "Male",
Height = 175,
Weight = 70,
IsSmoker = false,
CityTier = 1,
SumInsured = 10_00_000,
PreExistingConditions = new List<string>(),
Occupation = "IT Professional",
Members = new List<HealthMember> { new() { Age = 30 } }
};
// Act
var result = await calculator.CalculatePremiumAsync(
riskProfile, CreateTestProductConfig());
// Assert
Assert.Equal(8500m, result.BasePremium);
Assert.True(result.LoadingApplied <= 0.1m); // Minimal loading for young, fit person
Assert.True(result.GST > 0); // GST must be charged
Assert.Equal(18, result.Breakdown.GSTRate);
Assert.True(result.TotalPayablePremium < 12000m); // Reasonable range
}
[Theory]
[InlineData(25, 6500)]
[InlineData(35, 12000)]
[InlineData(45, 22000)]
[InlineData(55, 45000)]
[InlineData(65, 85000)]
public async Task CalculatePremium_VariousAges_ReturnsExpectedRange(
int age, decimal maxExpectedPremium)
{
// Age-based premium should increase monotonically
var calculator = CreateCalculator();
var profile = CreateProfileForAge(age);
var result = await calculator.CalculatePremiumAsync(
profile, CreateTestProductConfig());
Assert.True(result.TotalPayablePremium <= maxExpectedPremium * 1.2m);
Assert.True(result.TotalPayablePremium > 0);
}
[Fact]
public async Task CalculatePremium_Smoker_20PercentHigherThanNonsmoker()
{
var calculator = CreateCalculator();
var nonsmokerProfile = CreateProfileForAge(35);
nonsmokerProfile.IsSmoker = false;
var smokerProfile = CreateProfileForAge(35);
smokerProfile.IsSmoker = true;
var nonsmokerResult = await calculator.CalculatePremiumAsync(
nonsmokerProfile, CreateTestProductConfig());
var smokerResult = await calculator.CalculatePremiumAsync(
smokerProfile, CreateTestProductConfig());
var premiumDifference = smokerResult.TotalPayablePremium
/ nonsmokerResult.TotalPayablePremium;
Assert.True(premiumDifference >= 1.3m); // At least 30% higher
Assert.True(premiumDifference <= 1.6m); // But not more than 60% higher
}
}
26. Interview Q&A
Q1: How would you design the quote aggregation system to ensure sub-3-second response times while querying 20+ insurers?
Answer: The key is a three-tier approach. First, use aggressive caching with a 15-minute TTL for quotes with identical risk parameters. For a given age, city tier, and sum insured combination, the same quote is likely to be generated, so Redis caching with composite keys achieves 80%+ hit rates. Second, parallelize all insurer API calls using Task.WhenAll with individual timeouts of 2 seconds. If an insurer times out, return partial results from the insurers that responded successfully — users prefer seeing 15 out of 20 quotes quickly over waiting for all 20. Third, implement pre-computed quote pools for common risk profiles (healthy 30-year-old male in Tier 1 city with 10 lakh coverage) that are refreshed every 30 minutes and served directly from the cache.
Q2: How do you handle the scenario where an insurer changes their premium after a user has already seen the quote?
Answer: This is a real-world problem because insurer premiums can change due to regulatory filings or rate revisions that take effect at any time. The solution involves three mechanisms. First, include a premium validity window in the quote response (typically 15-30 minutes for health insurance). If the user completes purchase within this window, honor the quoted premium. Second, at the point of purchase, re-fetch the quote from the insurer and compare. If the premium has changed, show the user the new premium and ask for confirmation before proceeding. Third, implement a premium lock feature where users can pay a small non-refundable fee to lock the premium for 24 hours while they decide. This is particularly useful for high-value life insurance policies.
Q3: How would you prevent the platform from being biased toward insurers that pay higher commissions?
Answer: This is both a technical and ethical challenge. Technically, implement a neutral ranking algorithm that never uses commission rate as a ranking feature. The default sort order should be by premium price or a composite value score that considers price, coverage quality, claim settlement ratio, and insurer reputation. Provide a "best value" default ranking that optimizes for coverage per rupee of premium. Log all ranking decisions and make them auditable. Allow users to sort by different criteria (cheapest, highest rated, most popular) and always show the commission disclosure in policy details. From a business perspective, implement a policy where the platform does not accept products where the commission exceeds regulatory norms, and publish a transparency report annually showing the correlation (or lack thereof) between commission rates and ranking positions.
Q4: How do you handle insurer API downtime without degrading the user experience?
Answer: Implement a resilience strategy with four layers. Circuit breakers prevent cascading failures by stopping requests to an insurer after repeated failures. Cached quotes from the last 30 minutes are served as "recent quotes" with a disclaimer that pricing may have changed. Fallback pricing from historical data provides estimated quotes when the real-time API is unavailable. Finally, implement a graceful degradation mode where the platform shows all available insurer quotes and marks unavailable insurers as "temporarily unable to provide quote — please check back in X minutes." The key principle is partial availability is better than no availability — users prefer seeing 15 out of 20 quotes immediately over waiting for a complete set.
Q5: How do you ensure data privacy and security for sensitive medical and financial information?
Answer: Security for an insurance marketplace requires defense in depth. At the transport layer, enforce TLS 1.3 for all API communication. At the application layer, implement field-level encryption for PII using AWS KMS with automatic key rotation every 90 days. Aadhaar numbers are hashed (never stored raw), PAN numbers are encrypted at the field level, and medical records are stored in a separate encrypted database with stricter access controls. At the access layer, implement role-based access control where agents can only see their assigned customers, and all access to PII is logged in an immutable audit trail stored in a separate SIEM system. At the infrastructure layer, deploy in a VPC with private subnets, use WAF to block SQL injection and XSS attacks, and conduct quarterly penetration tests by an external security firm. Data localization compliance requires all user data to remain within Indian data centers, so we use AWS Mumbai region with no cross-region replication for PII data.
Q6: How would you design the claims processing system to handle both cashless and reimbursement claims?
Answer: Cashless and reimbursement claims have fundamentally different workflows. For cashless claims, the system must coordinate with the hospital's TPA (Third Party Administrator) desk in real-time to get pre-authorization. This requires a low-latency integration with the insurer's cashless approval API and the hospital management system. The architecture uses an event-driven approach where the claim submission triggers a pre-authorization request, and the response (approved, denied, or partially approved) is pushed back to the hospital desk terminal and the customer's mobile app simultaneously. For reimbursement claims, the flow is more document-centric: the customer uploads bills, discharge summary, and medical reports through the app, which triggers OCR processing, automated document classification, and assignment to a claims adjuster. The adjuster reviews the claim against the policy terms, queries additional information if needed, and makes a settlement decision. Both workflows converge at the settlement stage where the approved amount is either paid directly to the hospital (cashless) or transferred to the customer's bank account (reimbursement).
Q7: What database sharding strategy would you use for the policy database?
Answer: Policy data should be sharded by user ID (consistent hashing with virtual nodes) because the vast majority of queries are user-scoped — a user views their own policies, files claims for their own policies, and renews their own policies. Cross-shard queries are rare and handled by a separate read-optimized aggregate store. The shard key is the user ID hash, which distributes load evenly across shards and ensures all data for a single user resides on the same shard. For policy number lookups (which happen when an agent or insurer references a policy), maintain a global secondary index on policy number that maps to the user ID shard. When a new policy is created, the system first writes to the user's shard, then asynchronously updates the global index. This design supports 50 million users with 4 policies each across 32 shards, with each shard handling approximately 6.25 million users and 25 million policy records.
Q8: How do you handle premium refund processing when a customer cancels within the free-look period?
Answer: Free-look period refunds are a regulatory mandate in India — life and health insurance policies must allow cancellation within 15-30 days of issuance with a full premium refund minus a proportional deduction for the period of coverage. The refund process is a saga: first, validate that the cancellation request falls within the free-look window by checking the policy issuance date. Second, calculate the proportional deduction based on the number of days the policy was active. Third, initiate the refund through the original payment method (which requires storing the payment gateway reference during purchase). Fourth, notify the insurer to cancel their records and update the policy status. Fifth, update the platform's commission records to reverse the previously booked commission. The entire refund saga must complete within 7 business days as mandated by IRDAI. If the original payment method is no longer valid (expired card, closed bank account), the system must provide an alternative refund mechanism like NEFT transfer to a verified bank account.