system-design50 min read

Design a Digital Life Insurance Platform (LIC-Style) — A Senior+ Guide | Ayodhyya

Design a Digital Life Insurance Platform (LIC-Style)

Building a full-stack insurance system — quotes, underwriting, policies, claims, compliance, and multi-channel distribution at scale

Senior+ Guide 60+ min read 10,000+ words Ayodhyya

Table of Contents

  1. Introduction — The Digital Life Insurance Landscape
  2. Life Insurance Fundamentals
  3. Requirements Gathering
  4. Capacity Estimation & Sizing
  5. Data Model & Storage Schema
  6. High-Level Architecture
  7. API Design
  8. Plan Catalog & Product Configuration
  9. Online Quote Engine
  10. Underwriting & Risk Assessment
  11. Policy Issuance Pipeline
  12. Premium Collection & Payment Gateway
  13. Policy Loan & Surrender
  14. Claim Settlement Process
  15. Beneficiary Management
  16. Agent Management System
  17. Customer Portal & Self-Service
  18. Document Vault
  19. Regulatory Compliance (IRDAI)
  20. Multi-Channel Distribution
  21. Renewal & Revival
  22. Analytics & Actuarial Reporting
  23. Cost Estimation
  24. Testing Strategy
  25. Interview Q&A

1. Introduction — The Digital Life Insurance Landscape

Life insurance is one of the oldest and most complex financial products in the world. In India alone, the Life Insurance Corporation (LIC) manages over 400 million policies with a total asset base exceeding ₹40 lakh crore. The global life insurance market is projected to reach $4.1 trillion by 2028, driven by growing awareness, digital adoption, and regulatory reforms that mandate digitization. Yet despite this massive scale, the technology stack underlying most life insurance operations remains decades old, built on COBOL mainframes and monolithic architectures that struggle to adapt to modern consumer expectations.

A digital life insurance platform must handle the entire lifecycle of a policy — from initial inquiry and quote generation, through underwriting and policy issuance, premium collection over decades, riders and endorsements, policy loans, surrenders, and ultimately claim settlement or maturity payout. This is fundamentally different from building an e-commerce site or a SaaS application because the temporal dimension spans decades, the regulatory requirements are extraordinarily strict, and the financial calculations involve actuarial science with probability distributions that must be precisely correct.

Consider the scale: a platform like LIC processes over 20 million premium payments per month, generates millions of policy documents, handles hundreds of thousands of death claims per year, and must maintain data integrity across all these operations for policies that may last 35 years or more. Every calculation must be auditable, every transaction must be reversible or compensatable, and every regulatory filing must be generated from a single source of truth.

The digital transformation of life insurance is not just about putting a mobile app on top of legacy systems. It requires a complete rethinking of the data model, the business process engine, the integration layer with payment gateways and KYC providers, the document management system, the agent management system, and the regulatory reporting infrastructure. This guide walks through designing such a platform from first principles, covering every major subsystem with production-grade architecture, C# code examples, and interview-level depth.

Interview Context: Designing a life insurance platform tests your ability to model complex domain entities with long lifecycles, implement state machines for policy workflows, design idempotent financial transactions, handle regulatory compliance, and build systems that maintain consistency across decades. This is among the most challenging system design questions in the fintech domain.

2. Life Insurance Fundamentals

Types of Life Insurance Products

Understanding the core product types is essential before designing the data model, because each product type has fundamentally different premium calculation logic, benefit structures, and regulatory requirements.

Product TypeDescriptionPremiumBenefitExample
Term LifePure protection for a fixed termLowDeath benefit onlyLIC Tech Term Plan
Whole LifeCoverage until age 99-100ModerateDeath benefit + guaranteed maturityLIC Whole Life Plan
EndowmentSavings + protection hybridHighGuaranteed maturity + death benefitLIC Jeevan Anand
ULIPMarket-linked investmentVariableNAV-based fund valueLIC SIIP
Money BackPeriodic survival benefitsHighSurvival payouts + maturityLIC New Money Back
Pension/AnnuityRetirement income streamVariesRegular pension paymentsLIC Jeevan Akshay
Group TermEmployer-sponsored coverageLow per memberDeath benefit for groupCorporate Group Term

Key Financial Concepts

The system must accurately compute and store several financial concepts that form the backbone of every policy. Understanding mortality tables is critical because they define the probability of death at each age, which directly affects pricing. The Indian Mortality Table published by the Insurance Regulatory and Development Authority of India (IRDAI) is the standard reference for Indian life insurers. The mortality rate at age 30 might be 0.00106 (meaning approximately 1.06 deaths per 1,000 lives), while at age 60 it might be 0.01287 — these rates increase exponentially with age following a Gompertz-Makeham distribution.

Actuarial present value calculations discount future cash flows using an assumed interest rate (the investment return assumption). For a typical Indian endowment plan, the assumed investment return rate is between 4% and 6% per annum, published annually by the insurer and approved by IRDAI. The net level premium is the level annual premium that, when accumulated at the assumed interest rate, exactly equals the sum assured payable at the end of the term, adjusted for mortality. Loading factors cover acquisition costs (agent commissions, medical examination fees), administration costs, and a margin for contingencies.

The mathematical formula for a net single premium for a whole life policy at age x for a sum assured of S with interest rate i and mortality from the qx table is: NSPx = S times the sum of (tPx times qx+t divided by (1+i)^(t+1)) for all t from 0 to omega-x-1. The annual premium is then NSPx divided by the annuity-due factor a-double-dot-x. These calculations must be implemented with extreme precision using decimal arithmetic, not floating point, because even rounding errors of a few paise compounded over 30 years can result in significant discrepancies.

Policy Lifecycle States

A policy in a digital insurance platform traverses through many states over its lifetime. Understanding these states is critical for designing the state machine that governs all policy operations.

stateDiagram-v2 [*] --> QuoteGenerated QuoteGenerated --> ApplicationSubmitted ApplicationSubmitted --> UnderReview UnderReview --> MedicalExamRequired MedicalExamRequired --> UnderReview UnderReview --> Approved UnderReview --> Declined Approved --> PolicyIssued PolicyIssued --> Active Active --> PremiumDue Active --> Lapsed Active --> Surrendered Active --> Matured PremiumDue --> Active PremiumDue --> Lapsed Lapsed --> Active : Revival Lapsed --> Terminated Active --> PolicyLoan PolicyLoan --> Active

The state machine above represents the simplified version. In production, there are additional sub-states like "Grace Period," "Policy Loan Pending Approval," "Claim Under Investigation," "Claim Approved," "Claim Paid," "Maturity Claim Filed," and "Revival Under Review." Each state transition must be logged with a timestamp, the triggering user or system, and any associated financial transaction. The state machine must enforce valid transitions — for example, you cannot move a policy from "Terminated" back to "Active" under any circumstances.

3. Requirements Gathering

Functional Requirements

#RequirementPriorityDetails
F1Plan catalog browsingMustCustomers browse available plans with details, benefits, illustrations
F2Online quote generationMustInstant premium calculation based on age, sum assured, term, riders
F3Online applicationMustDigital application with e-KYC, proposal form, medical declaration
F4E-KYC verificationMustAadhaar-based OTP, PAN verification, video KYC
F5Payment gateway integrationMustUPI, net banking, credit/debit cards, NEFT, auto-debit
F6Policy document generationMustPDF policy bond, endorsement letters, premium receipts
F7Premium collectionMustSingle, annual, quarterly, monthly, semi-annual modes
F8Claim filingMustDeath claim, maturity claim, rider claim initiation online
F9Beneficiary nominationMustAdd, update, view nominees and their share percentages
F10Policy loanShouldApply for policy loan against surrender value
F11Surrender requestShouldOnline surrender with instant calculation and payout
F12Agent portalShouldAgent dashboard, lead management, commission tracking
F13Renewal remindersShouldSMS, email, push notifications for upcoming premiums
F14Policy revivalShouldRevive lapsed policies with premium revival calculator
F15WhatsApp integrationNicePolicy status, premium reminders via WhatsApp Business API
F16Chatbot assistanceNiceAI-powered chatbot for FAQs and basic policy queries

Non-Functional Requirements

RequirementTargetRationale
Availability99.95%Financial platform with regulatory uptime obligations
Latency (P99)< 2s for quotes, < 5s for policy generationCustomers expect near-instant quotes; PDFs take time
Throughput50,000 quotes/day, 10,000 policies/dayPeak during campaign periods and year-end
Data retentionMinimum 25 years post policy maturityRegulatory requirement for long-term policies
ConsistencyStrong consistency for financial transactionsPremium payments and claim settlements must be exact
SecuritySOC 2 Type II, IRDAI complianceSensitive financial and personal data
EncryptionAES-256 at rest, TLS 1.3 in transitKYC documents, PAN, Aadhaar data protection
IRDAI Mandate: The Insurance Regulatory and Development Authority of India mandates that all insurers maintain data within Indian data centers, file product approvals before launch, maintain solvency margins of 150%, and submit quarterly returns on policy counts, claims, and financial health. The platform must generate these reports automatically.

4. Capacity Estimation & Sizing

Traffic Estimation

Let us estimate the scale for a mid-sized digital insurer targeting 5 million active policies. With 5 million policies, annual premium payments generate approximately 5 million annual premiums or 15 million quarterly premiums. Assuming a 60/40 split between annual and quarterly modes, we get roughly 3 million annual and 12 million quarterly premium payments per year. During peak months (March in India for tax-saving), premium collection traffic spikes 3x. This means approximately 4 million premium payment transactions per month at peak, or about 1,500 transactions per second during peak hours.

Quote generation is a lighter operation but still significant. If the website receives 500,000 unique visitors per month and 5% request a quote, that is 25,000 quotes per month or about 833 per day. However, during marketing campaigns this could spike to 10x, reaching 8,333 quotes per day. Claim submissions are much rarer — with 5 million policies and a crude mortality rate of 6 per 1,000 annually, we expect approximately 30,000 death claims per year or about 82 per day. Maturity claims depend on the policy mix but are similarly modest in frequency.

Storage Estimation

Data TypeRecord SizeAnnual RecordsAnnual Storage25-Year Storage
Policy master2 KB500,000 new1 GB25 GB
Premium transactions0.5 KB15,000,0007.5 GB188 GB
Claims5 KB30,000150 MB3.75 GB
Documents (PDFs)500 KB avg2,000,0001 TB25 TB
Agent data1 KB50,00050 MB1.25 GB
Audit logs0.2 KB50,000,00010 GB250 GB

Total structured data for 25 years is approximately 470 GB. Documents dominate at 25 TB and must be stored in object storage like S3 or Azure Blob with lifecycle policies for tiered storage. The audit log is significant because every state change, every financial transaction, and every document access must be logged for regulatory compliance. We can compress audit logs after 3 years and archive them to cold storage, reducing the active storage requirement to about 60 GB for audit data.

Bandwidth Estimation

For the web and mobile applications, assuming 100,000 daily active users (DAUs) with an average session of 5 pages at 200 KB per page, the daily bandwidth is approximately 100 GB for serving content. API traffic for premium payments and quotes adds another 50 GB daily. Total daily bandwidth is approximately 150 GB, which is well within the capacity of a standard cloud deployment. Document downloads for policy bonds and claim forms could add another 200 GB daily during peak periods.

Key Insight: The document storage and generation pipeline is the most expensive component of this system. A single policy may generate 50+ PDF documents over its lifetime (policy bond, annual statements, premium receipts, endorsement letters, claim forms). Optimizing PDF generation with templates and caching partially computed documents can reduce compute costs by 40%.

5. Data Model & Storage Schema

The data model for a life insurance platform is exceptionally complex because it must model products with configurable benefit structures, policies with multi-decade lifecycles, financial transactions with double-entry bookkeeping, and regulatory-mandated data retention. The design below uses a relational database (PostgreSQL) as the system of record with Redis for caching and MongoDB for document metadata.

Core Tables

SQL
-- Insurance Product / Plan definitions
CREATE TABLE insurance_products (
    product_id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    product_code        VARCHAR(20) UNIQUE NOT NULL,
    product_name        VARCHAR(200) NOT NULL,
    product_type        VARCHAR(30) NOT NULL,
    description         TEXT,
    min_sum_assured     DECIMAL(15,2) NOT NULL,
    max_sum_assured     DECIMAL(15,2) NOT NULL,
    min_entry_age       INT NOT NULL,
    max_entry_age       INT NOT NULL,
    min_term_years      INT NOT NULL,
    max_term_years      INT NOT NULL,
    premium_modes       VARCHAR(50)[] NOT NULL,
    assumed_interest    DECIMAL(5,4) NOT NULL,
    mortality_table     VARCHAR(50) NOT NULL,
    status              VARCHAR(20) DEFAULT 'DRAFT',
    approved_by_irdai   BOOLEAN DEFAULT FALSE,
    irdai_approval_no   VARCHAR(50),
    effective_date      DATE NOT NULL,
    created_at          TIMESTAMPTZ DEFAULT NOW(),
    updated_at          TIMESTAMPTZ DEFAULT NOW()
);

-- Customer master
CREATE TABLE customers (
    customer_id         UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    customer_number     VARCHAR(20) UNIQUE NOT NULL,
    first_name          VARCHAR(100) NOT NULL,
    last_name           VARCHAR(100) NOT NULL,
    date_of_birth       DATE NOT NULL,
    gender              VARCHAR(10) NOT NULL,
    email               VARCHAR(200),
    phone               VARCHAR(15) NOT NULL,
    aadhaar_hash        VARCHAR(64),
    pan_number          VARCHAR(10),
    address_line1       VARCHAR(200),
    address_line2       VARCHAR(200),
    city                VARCHAR(100),
    state               VARCHAR(100),
    pincode             VARCHAR(6),
    occupation          VARCHAR(100),
    annual_income       DECIMAL(15,2),
    kyc_status          VARCHAR(20) DEFAULT 'PENDING',
    kyc_verified_at     TIMESTAMPTZ,
    created_at          TIMESTAMPTZ DEFAULT NOW(),
    updated_at          TIMESTAMPTZ DEFAULT NOW()
);

-- Policy master (the central entity)
CREATE TABLE policies (
    policy_id           UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    policy_number       VARCHAR(20) UNIQUE NOT NULL,
    customer_id         UUID NOT NULL REFERENCES customers(customer_id),
    product_id          UUID NOT NULL REFERENCES insurance_products(product_id),
    agent_id            UUID REFERENCES agents(agent_id),
    sum_assured         DECIMAL(15,2) NOT NULL,
    policy_term         INT NOT NULL,
    premium_term        INT NOT NULL,
    premium_amount      DECIMAL(12,2) NOT NULL,
    premium_mode        VARCHAR(20) NOT NULL,
    modal_loading       DECIMAL(5,4) NOT NULL,
    base_premium        DECIMAL(12,2) NOT NULL,
    gst_amount          DECIMAL(12,2) NOT NULL,
    total_premium       DECIMAL(12,2) NOT NULL,
    policy_status       VARCHAR(30) NOT NULL DEFAULT 'ISSUED',
    issue_date          DATE NOT NULL,
    inception_date      DATE NOT NULL,
    maturity_date       DATE NOT NULL,
    first_premium_date  DATE NOT NULL,
    last_premium_date   DATE,
    surrender_value     DECIMAL(15,2) DEFAULT 0,
    paid_up_sum_assured DECIMAL(15,2) DEFAULT 0,
    revival_deadline    DATE,
    created_at          TIMESTAMPTZ DEFAULT NOW(),
    updated_at          TIMESTAMPTZ DEFAULT NOW()
);

-- Premium transactions (double-entry bookkeeping)
CREATE TABLE premium_transactions (
    transaction_id      UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    policy_id           UUID NOT NULL REFERENCES policies(policy_id),
    transaction_type    VARCHAR(30) NOT NULL,
    amount              DECIMAL(12,2) NOT NULL,
    gst_amount          DECIMAL(12,2) NOT NULL,
    total_amount        DECIMAL(12,2) NOT NULL,
    payment_mode        VARCHAR(30) NOT NULL,
    payment_gateway_ref VARCHAR(100),
    status              VARCHAR(20) NOT NULL,
    transaction_date    TIMESTAMPTZ NOT NULL,
    settlement_date     TIMESTAMPTZ,
    created_at          TIMESTAMPTZ DEFAULT NOW()
);

-- Claims
CREATE TABLE claims (
    claim_id            UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    claim_number        VARCHAR(20) UNIQUE NOT NULL,
    policy_id           UUID NOT NULL REFERENCES policies(policy_id),
    claim_type          VARCHAR(30) NOT NULL,
    claim_status        VARCHAR(30) NOT NULL DEFAULT 'FILED',
    claim_amount        DECIMAL(15,2),
    settlement_amount   DECIMAL(15,2),
    filing_date         DATE NOT NULL,
    investigation_start DATE,
    settlement_date     DATE,
    payment_date        DATE,
    cause_of_death      TEXT,
    hospital_details    JSONB,
    assignee_id         UUID REFERENCES users(user_id),
    created_at          TIMESTAMPTZ DEFAULT NOW(),
    updated_at          TIMESTAMPTZ DEFAULT NOW()
);

-- Beneficiaries / Nominees
CREATE TABLE beneficiaries (
    beneficiary_id      UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    policy_id           UUID NOT NULL REFERENCES policies(policy_id),
    customer_id         UUID REFERENCES customers(customer_id),
    name                VARCHAR(200) NOT NULL,
    relationship        VARCHAR(50) NOT NULL,
    date_of_birth       DATE,
    share_percentage    DECIMAL(5,2) NOT NULL,
    aadhaar_hash        VARCHAR(64),
    is_primary          BOOLEAN DEFAULT FALSE,
    created_at          TIMESTAMPTZ DEFAULT NOW()
);

-- Policy loans
CREATE TABLE policy_loans (
    loan_id             UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    policy_id           UUID NOT NULL REFERENCES policies(policy_id),
    loan_amount         DECIMAL(15,2) NOT NULL,
    outstanding_amount  DECIMAL(15,2) NOT NULL,
    interest_rate       DECIMAL(5,4) NOT NULL,
    disbursement_date   DATE NOT NULL,
    status              VARCHAR(20) NOT NULL,
    created_at          TIMESTAMPTZ DEFAULT NOW()
);

-- Document vault
CREATE TABLE documents (
    document_id         UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    entity_type         VARCHAR(50) NOT NULL,
    entity_id           UUID NOT NULL,
    document_type       VARCHAR(50) NOT NULL,
    file_name           VARCHAR(200) NOT NULL,
    file_size           BIGINT NOT NULL,
    mime_type           VARCHAR(50) NOT NULL,
    storage_key         VARCHAR(500) NOT NULL,
    checksum_sha256     VARCHAR(64) NOT NULL,
    uploaded_by         UUID NOT NULL,
    created_at          TIMESTAMPTZ DEFAULT NOW()
);

-- Agents
CREATE TABLE agents (
    agent_id            UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    agent_code          VARCHAR(20) UNIQUE NOT NULL,
    name                VARCHAR(200) NOT NULL,
    email               VARCHAR(200) NOT NULL,
    phone               VARCHAR(15) NOT NULL,
    agency_code         VARCHAR(50),
    branch_code         VARCHAR(50),
    commission_rate      DECIMAL(5,4),
    status              VARCHAR(20) DEFAULT 'ACTIVE',
    appointed_date      DATE NOT NULL,
    created_at          TIMESTAMPTZ DEFAULT NOW()
);

Indexing Strategy

SQL
CREATE INDEX idx_policies_customer ON policies(customer_id);
CREATE INDEX idx_policies_status ON policies(policy_status) WHERE policy_status != 'TERMINATED';
CREATE INDEX idx_policies_maturity ON policies(maturity_date) WHERE policy_status = 'ACTIVE';
CREATE INDEX idx_premium_txn_policy ON premium_transactions(policy_id, transaction_date);
CREATE INDEX idx_premium_txn_date ON premium_transactions(transaction_date) WHERE status = 'SETTLED';
CREATE INDEX idx_claims_status ON claims(claim_status);
CREATE INDEX idx_claims_filing ON claims(filing_date);
CREATE INDEX idx_documents_entity ON documents(entity_type, entity_id);
CREATE INDEX idx_customers_phone ON customers(phone);
CREATE INDEX idx_policies_number_lookup ON policies(policy_number);

The indexing strategy prioritizes the most common lookup patterns: customer-to-policies (for customer service), policy status queries (for operational dashboards), maturity date range (for maturity claim processing), and premium transaction history (for financial reconciliation). Partial indexes on active policies and settled transactions keep index sizes manageable while covering the hot path queries efficiently.

6. High-Level Architecture

graph TB subgraph Clients WEB[Web Portal] MOB[Mobile App] AGENT[Agent Portal] API[Partner API] end subgraph Gateway GW[API Gateway / Kong] AUTH[Auth Service] RATE[Rate Limiter] end subgraph Core Services CUST[Customer Service] QUOTE[Quote Engine] PROD[Product Service] UW[Underwriting Service] POL[Policy Service] PREM[Premium Service] CLAIM[Claim Service] LOAN[Loan Service] BEN[Beneficiary Service] AGTS[Agent Service] DOC[Document Service] NOTIF[Notification Service] COMP[Compliance Service] end subgraph Data Layer PG[(PostgreSQL)] REDIS[(Redis)] MONGO[(MongoDB)] S3[(S3 / Blob)] ES[(Elasticsearch)] end subgraph External KYC[KYC Provider] PAY[Payment Gateway] SMS[SMS Gateway] EMAIL[Email Service] IRDAI[IRDAI Filing System] BANK[Bank NEFT/RTGS] end WEB --> GW MOB --> GW AGENT --> GW API --> GW GW --> AUTH GW --> RATE GW --> CUST GW --> QUOTE GW --> PROD GW --> UW GW --> POL GW --> PREM GW --> CLAIM GW --> LOAN GW --> BEN GW --> AGTS GW --> DOC GW --> NOTIF GW --> COMP CUST --> PG QUOTE --> REDIS PROD --> PG UW --> PG POL --> PG PREM --> PG CLAIM --> PG LOAN --> PG BEN --> PG AGTS --> PG DOC --> S3 DOC --> MONGO NOTIF --> REDIS COMP --> ES UW --> KYC PREM --> PAY PREM --> BANK NOTIF --> SMS NOTIF --> EMAIL COMP --> IRDAI

The architecture follows a microservices pattern where each bounded context in the insurance domain maps to a dedicated service. The policy service is the most critical and complex, as it manages the state machine for all policy lifecycle events. The premium service handles all financial transactions and integrates with multiple payment gateways. The claim service orchestrates the multi-step claim settlement workflow including document verification, investigation assignment, and settlement disbursement.

The communication between services uses a hybrid approach: synchronous REST for request-response patterns (quote generation, customer lookup) and asynchronous events via Kafka for eventual consistency patterns (premium payment confirmation triggers policy status update, claim filing triggers notification). This separation ensures that the premium collection path is optimized for low latency while claim processing can be asynchronous without impacting customer experience.

Domain-Driven Design: Each microservice owns its data completely — no direct database access across service boundaries. Inter-service communication uses well-defined APIs and domain events. This isolation allows independent scaling, deployment, and evolution of each subsystem.

7. API Design

Core API Endpoints

REST
GET    /api/v1/products                          -- List all products
GET    /api/v1/products/{productId}              -- Get product details
GET    /api/v1/products/{productId}/brochure     -- Download product brochure

POST   /api/v1/quotes                            -- Generate quote
POST   /api/v1/quotes/{quoteId}/apply            -- Convert quote to application

POST   /api/v1/applications                      -- Submit application
GET    /api/v1/applications/{appId}              -- Get application status
PUT    /api/v1/applications/{appId}/medical      -- Upload medical reports

POST   /api/v1/policies                          -- Issue policy (admin)
GET    /api/v1/policies/{policyNumber}           -- Get policy details
GET    /api/v1/policies/{policyNumber}/statements -- Premium statement history
POST   /api/v1/policies/{policyNumber}/revive    -- Revive lapsed policy

POST   /api/v1/premiums/initiate                 -- Initiate premium payment
POST   /api/v1/premiums/{txnId}/confirm         -- Confirm payment
GET    /api/v1/premiums/{policyNumber}/schedule  -- Get payment schedule

POST   /api/v1/claims/file                       -- File a claim
POST   /api/v1/claims/{claimId}/documents        -- Upload claim documents
GET    /api/v1/claims/{claimId}                  -- Get claim status

POST   /api/v1/loans/apply                       -- Apply for policy loan
POST   /api/v1/loans/{loanId}/repay              -- Repay loan

GET    /api/v1/customers/{customerId}/policies   -- Customer's policies
GET    /api/v1/customers/{customerId}/nominees   -- Customer's nominees

POST   /api/v1/agents/{agentId}/leads            -- Create lead
GET    /api/v1/agents/{agentId}/dashboard        -- Agent dashboard

Quote Request / Response

JSON
// POST /api/v1/quotes
{
    "productCode": "JEEVAN_ANAND_815",
    "sumAssured": 5000000,
    "policyTerm": 25,
    "premiumTerm": 20,
    "premiumMode": "ANNUAL",
    "dateOfBirth": "1990-06-15",
    "gender": "MALE",
    "smoker": false,
    "riders": [
        { "riderCode": "TERM_RIDER", "sumAssured": 5000000 },
        { "riderCode": "ACCIDENT_RIDER", "sumAssured": 5000000 }
    ],
    "state": "MAHARASHTRA"
}

// Response 200
{
    "quoteId": "QTE-2026-07-13-ABCD1234",
    "productCode": "JEEVAN_ANAND_815",
    "basePremium": 297450.00,
    "riderPremiums": [
        { "riderCode": "TERM_RIDER", "premium": 12500.00 },
        { "riderCode": "ACCIDENT_RIDER", "premium": 4500.00 }
    ],
    "totalBasePremium": 314450.00,
    "gstAmount": 56501.00,
    "totalPayable": 370951.00,
    "maturityBenefit": 5000000.00,
    "deathBenefit": 5000000.00,
    "bonusProjection": {
        "conservative": 2100000,
        "moderate": 3200000,
        "optimistic": 4500000
    },
    "validUntil": "2026-07-20T23:59:59+05:30"
}

Policy Service Implementation

C#
public class PolicyService : IPolicyService
{
    private readonly IPolicyRepository _policyRepo;
    private readonly IPremiumRepository _premiumRepo;
    private readonly IEventPublisher _eventPublisher;
    private readonly ILogger<PolicyService> _logger;

    public async Task<PolicyResponse> IssuePolicyAsync(
        IssuePolicyRequest request,
        CancellationToken ct)
    {
        await using var transaction = await _policyRepo
            .BeginTransactionAsync(IsolationLevel.Serializable);

        try
        {
            var policyNumber = await GeneratePolicyNumberAsync(ct);

            var policy = new Policy
            {
                PolicyId = Guid.NewGuid(),
                PolicyNumber = policyNumber,
                CustomerId = request.CustomerId,
                ProductId = request.ProductId,
                AgentId = request.AgentId,
                SumAssured = request.SumAssured,
                PolicyTerm = request.PolicyTerm,
                PremiumTerm = request.PremiumTerm,
                PremiumAmount = request.BasePremium,
                PremiumMode = request.PremiumMode,
                ModalLoading = request.ModalLoading,
                BasePremium = request.BasePremium,
                GstAmount = request.GstAmount,
                TotalPremium = request.TotalPremium,
                PolicyStatus = PolicyStatus.Issued,
                IssueDate = DateOnly.FromDateTime(DateTime.UtcNow),
                InceptionDate = request.InceptionDate,
                MaturityDate = CalculateMaturityDate(
                    request.InceptionDate, request.PolicyTerm),
                FirstPremiumDate = request.InceptionDate,
                CreatedAt = DateTime.UtcNow,
                UpdatedAt = DateTime.UtcNow
            };

            await _policyRepo.AddAsync(policy, ct);

            var premiumTxn = new PremiumTransaction
            {
                TransactionId = Guid.NewGuid(),
                PolicyId = policy.PolicyId,
                TransactionType = "FIRST_PREMIUM",
                Amount = request.BasePremium,
                GstAmount = request.GstAmount,
                TotalAmount = request.TotalPremium,
                PaymentMode = request.PaymentMode,
                PaymentGatewayRef = request.PaymentRef,
                Status = "SETTLED",
                TransactionDate = DateTime.UtcNow,
                SettlementDate = DateTime.UtcNow
            };

            await _premiumRepo.AddAsync(premiumTxn, ct);
            await transaction.CommitAsync(ct);

            await _eventPublisher.PublishAsync(new PolicyIssuedEvent
            {
                PolicyNumber = policyNumber,
                CustomerId = request.CustomerId,
                IssuedAt = DateTime.UtcNow
            }, ct);

            _logger.LogInformation(
                "Policy {PolicyNumber} issued for customer {CustomerId}",
                policyNumber, request.CustomerId);

            return MapToResponse(policy);
        }
        catch (Exception ex)
        {
            await transaction.RollbackAsync(ct);
            _logger.LogError(ex,
                "Failed to issue policy for customer {CustomerId}",
                request.CustomerId);
            throw;
        }
    }

    private async Task<string> GeneratePolicyNumberAsync(
        CancellationToken ct)
    {
        var year = DateTime.UtcNow.Year;
        var sequence = await _policyRepo
            .GetNextSequenceAsync($"POL-{year}", ct);
        return $"POL-{year}-{sequence:D7}";
    }

    private DateOnly CalculateMaturityDate(
        DateOnly inceptionDate, int termYears)
    {
        return inceptionDate.AddYears(termYears);
    }
}

8. Plan Catalog & Product Configuration

The plan catalog is the foundation of the entire insurance platform because it defines the rules, limits, and calculation parameters for every product. Rather than hardcoding product logic, a well-designed platform uses a configurable product engine where benefit structures, rider options, premium loading factors, and eligibility criteria are all stored as data. This allows product managers to launch new products or modify existing ones without code deployments, subject to regulatory approval.

Each product configuration includes mortality table references, interest rate assumptions, surrender value factors, paid-up value factors, and bonus declaration rules. The configuration must support multi-versioning because products approved by IRDAI may have different parameters for different policy years. For example, the bonus rate for LIC's Jeevan Anand might change from year to year based on the insurer's investment performance, and the system must calculate bonuses using the rate applicable to each policy year, not the current rate.

C#
public class ProductConfiguration
{
    public string ProductCode { get; set; }
    public string ProductName { get; set; }
    public ProductType ProductType { get; set; }
    public EligibilityCriteria Eligibility { get; set; }
    public PremiumStructure Premium { get; set; }
    public BenefitStructure Benefits { get; set; }
    public List<RiderConfig> AvailableRiders { get; set; }
    public SurrenderFactors Surrender { get; set; }
    public List<BonusDeclaration> BonusHistory { get; set; }
}

public class EligibilityCriteria
{
    public int MinEntryAge { get; set; }
    public int MaxEntryAge { get; set; }
    public int MinSumAssured { get; set; }
    public int MaxSumAssured { get; set; }
    public int MinTerm { get; set; }
    public int MaxTerm { get; set; }
    public decimal MinIncome { get; set; }
    public List<string> AllowedPremiumModes { get; set; }
}

public class PremiumStructure
{
    public decimal AssumedInterestRate { get; set; }
    public string MortalityTable { get; set; }
    public decimal ModalLoadingFactor { get; set; }
    public decimal GSTRate { get; set; }
    public Dictionary<string, decimal> ModeFactors { get; set; }
}

public class BenefitStructure
{
    public bool HasMaturityBenefit { get; set; }
    public bool HasDeathBenefit { get; set; }
    public bool HasSurvivalBenefit { get; set; }
    public decimal GuaranteedAdditionRate { get; set; }
    public decimal LoyaltyAdditionRate { get; set; }
    public List<SurvivalBenefitSchedule> SurvivalBenefits { get; set; }
    public string BonusCalculationMethod { get; set; }
}

public class SurrenderFactors
{
    public int MinPaidYearsForSurrender { get; set; }
    public decimal FirstYearSurrenderFactor { get; set; }
    public List<YearFactorPair> YearWiseFactors { get; set; }
}

public class BonusDeclaration
{
    public int PolicyYear { get; set; }
    public decimal ReversionaryBonusRate { get; set; }
    public decimal TerminalBonusRate { get; set; }
    public DateOnly DeclarationDate { get; set; }
}

public class RiderConfig
{
    public string RiderCode { get; set; }
    public string RiderName { get; set; }
    public RiderType Type { get; set; }
    public decimal PremiumRatePerThousand { get; set; }
    public int MaxSumAssured { get; set; }
    public bool RequiresMedicalExam { get; set; }
}
Product Versioning: When IRDAI approves a product modification (e.g., change in interest rate assumption), the system must create a new version of the product configuration. Existing policies continue to be governed by the version in force at their inception, while new policies use the latest approved version. This means every premium calculation must reference the product version snapshot stored with the policy record.

9. Online Quote Engine

The quote engine is the most computationally intensive real-time component of the platform. It must calculate premiums accurately using actuarial formulas, support multiple riders, project future benefits including bonus estimates, and return results within 500 milliseconds. The engine must handle the complexity of different mortality tables, interest rate assumptions, and modal loading factors for each product.

Premium Calculation Flow

flowchart TD A[Quote Request] --> B{Validate Input} B -->|Invalid| C[Return Validation Error] B -->|Valid| D[Load Product Config] D --> E[Load Mortality Table] E --> F[Calculate Net Single Premium] F --> G[Apply Loading Factors] G --> H[Calculate Base Annual Premium] H --> I{Premium Mode?} I -->|Annual| J[No modal factor] I -->|Semi-Annual| K[Apply 0.515 factor] I -->|Quarterly| L[Apply 0.262 factor] I -->|Monthly| M[Apply 0.088 factor] J --> N[Add GST] K --> N L --> N M --> N N --> O[Calculate Rider Premiums] O --> P[Project Maturity Benefits] P --> Q[Return Quote Response]
C#
public class QuoteEngine : IQuoteEngine
{
    private readonly IMortalityTableService _mortalityService;
    private readonly IProductRepository _productRepo;
    private readonly IBonusProjectionService _bonusService;

    public async Task<QuoteResult> CalculateQuoteAsync(
        QuoteRequest request,
        CancellationToken ct)
    {
        var product = await _productRepo
            .GetActiveVersionAsync(request.ProductCode, ct);
        var mortalityTable = await _mortalityService
            .GetTableAsync(product.Premium.MortalityTable, ct);

        var age = CalculateAge(request.DateOfBirth);

        ValidateEligibility(product, age, request.SumAssured,
            request.PolicyTerm);

        var netSinglePremium = CalculateNSP(
            age,
            request.SumAssured,
            product.Premium.AssumedInterestRate,
            mortalityTable,
            request.PolicyTerm);

        var loadedPremium = ApplyLoadingFactors(
            netSinglePremium,
            product.Premium.ModalLoadingFactor);

        var annualPremium = loadedPremium / request.PremiumTerm;

        var modalPremium = ApplyModalFactor(
            annualPremium,
            request.PremiumMode,
            product.Premium.ModeFactors);

        var gstAmount = modalPremium * product.Premium.GSTRate;

        var riderPremiums = await CalculateRiderPremiumsAsync(
            product.AvailableRiders,
            request.Riders,
            age,
            request.SumAssured,
            ct);

        var maturityProjection = await _bonusService
            .ProjectMaturityAsync(
                product,
                request.SumAssured,
                request.PolicyTerm,
                ct);

        return new QuoteResult
        {
            QuoteId = GenerateQuoteId(),
            BasePremium = Math.Round(annualPremium, 2),
            ModalPremium = Math.Round(modalPremium, 2),
            GstAmount = Math.Round(gstAmount, 2),
            TotalPayable = Math.Round(modalPremium + gstAmount, 2),
            RiderPremiums = riderPremiums,
            MaturityBenefit = request.SumAssured,
            BonusProjection = maturityProjection,
            ValidUntil = DateTime.UtcNow.AddDays(7)
        };
    }

    private decimal CalculateNSP(
        int age,
        decimal sumAssured,
        decimal interestRate,
        decimal[] mortalityTable,
        int term)
    {
        decimal nsp = 0;
        decimal discountFactor = 1m;

        for (int t = 0; t < term; t++)
        {
            decimal mortalityRate = mortalityTable[age + t];
            decimal survivalProb = 1m - mortalityRate;
            decimal expectedPayout = sumAssured * mortalityRate;
            nsp += expectedPayout * discountFactor;
            discountFactor /= (1m + interestRate);
        }

        return nsp;
    }

    private decimal ApplyModalFactor(
        decimal annualPremium,
        string mode,
        Dictionary<string, decimal> factors)
    {
        return annualPremium * factors[mode];
    }
}

The quote engine uses Redis caching for product configurations and mortality tables since these change very infrequently (at most annually when new bonus rates are declared). A quote result is cached for 7 days with the quote ID as the key, allowing the application flow to reference the exact quote that was generated. The premium calculation uses decimal throughout to avoid floating-point precision issues that could result in incorrect premium amounts.

10. Underwriting & Risk Assessment

Underwriting is the process of evaluating the risk posed by a prospective policyholder and deciding whether to accept the risk, decline it, or accept it with modifications (such as a higher premium or an exclusion clause). In a digital platform, underwriting must balance speed (customers expect instant decisions for simple policies) with accuracy (errors in underwriting can result in massive losses for the insurer).

Underwriting Decision Matrix

Risk FactorLow RiskMedium RiskHigh RiskDecision
Age18-3536-5051-65Age affects mortality rate
Sum Assured< 25L25L - 1Cr> 1CrHigher SA needs more scrutiny
BMI18.5-2525-30<18.5 or >30Obesity increases mortality
SmokingNon-smokerEx-smoker 3yr+Current smokerSmoker loading 30-50%
Family HistoryNo critical illnessOne parent CVDHereditary conditionsAffects long-term risk
Income> 5x premium3-5x premium< 3x premiumEnsures affordability
Medical HistoryNo conditionsControlled hypertensionDiabetes, heart diseaseMay require extra medical
C#
public class UnderwritingEngine : IUnderwritingEngine
{
    public async Task<UnderwritingDecision> EvaluateAsync(
        UnderwritingRequest request,
        CancellationToken ct)
    {
        var score = 0;
        var flags = new List<UnderwritingFlag>();

        score += EvaluateAge(request.DateOfBirth);
        score += EvaluateBMI(request.Weight, request.Height);
        score += EvaluateSmokingStatus(request.Smoker);
        score += EvaluateIncome(
            request.AnnualIncome, request.ProposedPremium);
        score += EvaluateFamilyHistory(request.FamilyHistory);
        score += await EvaluateMedicalHistoryAsync(
            request.CustomerId, ct);

        if (request.SumAssured > 10_000_000m)
        {
            flags.Add(UnderwritingFlag.HIGH_SUM_ASSURED);
            score += 15;
        }

        if (request.Age > 50)
        {
            flags.Add(UnderwritingFlag.SENIOR_ENTRY);
            score += 10;
        }

        var decision = score switch
        {
            <= 20 => UnderwritingDecision.AutoApproved,
            <= 40 => UnderwritingDecision.MedicalExamRequired,
            <= 60 => UnderwritingDecision.ManualReview,
            _ => UnderwritingDecision.Declined
        };

        if (decision == UnderwritingDecision.MedicalExamRequired)
        {
            await ScheduleMedicalExamAsync(request.ApplicationId, ct);
        }

        return new UnderwritingDecision
        {
            Decision = decision,
            RiskScore = score,
            Flags = flags,
            EvaluatedAt = DateTime.UtcNow,
            NextSteps = GetNextSteps(decision)
        };
    }

    private int EvaluateAge(DateOnly dateOfBirth)
    {
        var age = CalculateAge(dateOfBirth);
        return age switch
        {
            <= 25 => 5,
            <= 35 => 10,
            <= 45 => 15,
            <= 55 => 25,
            _ => 35
        };
    }

    private int EvaluateBMI(decimal weightKg, decimal heightCm)
    {
        var heightM = heightCm / 100m;
        var bmi = weightKg / (heightM * heightM);
        return bmi switch
        {
            < 18.5m => 20,
            <= 25m => 5,
            <= 30m => 15,
            _ => 25
        };
    }
}

The underwriting engine uses a rules-based scoring system that assigns weights to various risk factors. For policies below a certain sum assured threshold (typically ₹25 lakh), the engine can make auto-decisions. For higher sum assured policies, the engine flags cases for manual underwriter review. Medical examination requirements are triggered based on the risk score and the product's specific underwriting guidelines. The entire decision process must be auditable because IRDAI can audit underwriting decisions during inspections.

11. Policy Issuance Pipeline

Once an application is approved by underwriting, the policy issuance pipeline generates the policy bond, creates all necessary records, sets up the premium payment schedule, and delivers the policy documents to the customer. This pipeline must handle edge cases like backdated policies (where the inception date is in the past for premium payment purposes), single-premium policies, and group policies where one application generates multiple individual policies.

Issuance Pipeline Stages

flowchart LR A[Underwriting Approved] --> B[Policy Number Generation] B --> C[Premium Schedule Creation] C --> D[Policy Bond PDF Generation] D --> E[Document Vault Upload] E --> F[Customer Notification] F --> G[Agent Commission Setup] G --> H[Regulatory Registration]
C#
public class PolicyIssuancePipeline
{
    private readonly IPolicyNumberGenerator _numberGen;
    private readonly IPremiumScheduleBuilder _scheduleBuilder;
    private readonly IPolicyBondPdfGenerator _pdfGen;
    private readonly IDocumentVaultService _docVault;
    private readonly INotificationService _notification;
    private readonly ICommissionService _commission;
    private readonly IRegulatoryService _regulatory;

    public async Task<IssuanceResult> ExecuteAsync(
        ApprovedApplication application,
        CancellationToken ct)
    {
        var policyNumber = await _numberGen.GenerateAsync(ct);

        var schedule = _scheduleBuilder.Build(
            application.BasePremium,
            application.PremiumMode,
            application.PremiumTerm,
            application.InceptionDate);

        var pdfBytes = await _pdfGen.GenerateAsync(
            new PolicyBondData
            {
                PolicyNumber = policyNumber,
                CustomerName = application.CustomerName,
                ProductName = application.ProductName,
                SumAssured = application.SumAssured,
                PremiumAmount = application.TotalPremium,
                PremiumMode = application.PremiumMode,
                PolicyTerm = application.PolicyTerm,
                InceptionDate = application.InceptionDate,
                MaturityDate = application.MaturityDate,
                Nominees = application.Nominees,
                TermsAndConditions = application.TermsAndConditions
            }, ct);

        var documentRef = await _docVault.UploadAsync(
            new DocumentUpload
            {
                EntityType = "POLICY",
                EntityId = policyNumber,
                DocumentType = "POLICY_BOND",
                FileName = $"{policyNumber}_bond.pdf",
                Content = pdfBytes,
                ContentType = "application/pdf"
            }, ct);

        await _notification.SendPolicyIssuanceAsync(
            application.CustomerId,
            policyNumber,
            ct);

        await _commission.SetupAsync(
            application.AgentId,
            policyNumber,
            application.TotalPremium,
            ct);

        await _regulatory.RegisterPolicyAsync(
            policyNumber,
            application,
            ct);

        return new IssuanceResult
        {
            PolicyNumber = policyNumber,
            DocumentId = documentRef.DocumentId,
            IssuedAt = DateTime.UtcNow
        };
    }
}

12. Premium Collection & Payment Gateway

Premium collection is the financial heartbeat of the insurance platform. Every payment must be idempotent, auditable, and reconcilable. The system must support multiple payment methods (UPI, net banking, credit cards, debit cards, NEFT/RTGS, auto-debit via NACH), handle payment failures gracefully, and maintain exact accounting records using double-entry bookkeeping principles.

Payment Flow

sequenceDiagram participant C as Customer participant API as API Gateway participant PS as Premium Service participant PG as Payment Gateway participant PGW as Bank/UPI participant ES as Event Store participant NS as Notification Service C->>API: Initiate Payment API->>PS: Create Payment Session PS->>PG: Create Order (amount, policy) PG-->>PS: Order ID PS->>ES: Log Payment Initiated PS-->>C: Redirect to PG / UPI App C->>PGW: Complete Payment PGW->>PG: Webhook (Success/Failure) PG->>PS: Payment Confirmation PS->>PS: Idempotency Check PS->>PS: Update Premium Transaction PS->>ES: Log Payment Settled PS->>NS: Send Receipt NS-->>C: SMS + Email Receipt
C#
public class PremiumCollectionService
{
    private readonly IPaymentGatewayFactory _pgFactory;
    private readonly IPremiumTransactionRepository _txnRepo;
    private readonly IPolicyRepository _policyRepo;
    private readonly IIdempotencyService _idempotency;
    private readonly IEventPublisher _events;

    public async Task<PaymentInitiationResult> InitiatePaymentAsync(
        InitiatePaymentRequest request,
        CancellationToken ct)
    {
        var policy = await _policyRepo
            .GetByNumberAsync(request.PolicyNumber, ct);

        if (policy.PolicyStatus == PolicyStatus.Lapsed)
        {
            throw new PolicyLapsedException(policy.PolicyNumber);
        }

        var expectedAmount = CalculateNextDueAmount(policy);

        if (Math.Abs(request.Amount - expectedAmount) > 0.01m)
        {
            throw new AmountMismatchException(
                expectedAmount, request.Amount);
        }

        var paymentSession = new PaymentSession
        {
            SessionId = Guid.NewGuid(),
            PolicyId = policy.PolicyId,
            Amount = request.Amount,
            PaymentMode = request.PaymentMode,
            IdempotencyKey = request.IdempotencyKey,
            CreatedAt = DateTime.UtcNow,
            ExpiresAt = DateTime.UtcNow.AddMinutes(30)
        };

        var gateway = _pgFactory.Create(request.PaymentMode);
        var orderResult = await gateway.CreateOrderAsync(
            new OrderRequest
            {
                Amount = request.Amount,
                Currency = "INR",
                Reference = paymentSession.SessionId.ToString(),
                Description = $"Premium for {policy.PolicyNumber}",
                CallbackUrl = $"/api/v1/premiums/webhook"
            }, ct);

        paymentSession.GatewayOrderId = orderResult.OrderId;

        await _txnRepo.SavePaymentSessionAsync(paymentSession, ct);

        return new PaymentInitiationResult
        {
            SessionId = paymentSession.SessionId,
            GatewayOrderId = orderResult.OrderId,
            PaymentUrl = orderResult.PaymentUrl,
            UpiIntentUrl = orderResult.UpiIntentUrl,
            ExpiresAt = paymentSession.ExpiresAt
        };
    }

    public async Task<PaymentConfirmationResult> ConfirmPaymentAsync(
        PaymentWebhook webhook,
        CancellationToken ct)
    {
        var idempotencyKey = webhook.OrderId;
        if (await _idempotency.ExistsAsync(idempotencyKey, ct))
        {
            return await _idempotency
                .GetResultAsync<PaymentConfirmationResult>(
                    idempotencyKey, ct);
        }

        await using var txn = await _txnRepo
            .BeginTransactionAsync(IsolationLevel.Serializable);

        try
        {
            var session = await _txnRepo
                .GetPaymentSessionByGatewayOrderAsync(
                    webhook.OrderId, ct);

            if (session == null)
            {
                throw new PaymentSessionNotFoundException(
                    webhook.OrderId);
            }

            if (session.Status == PaymentStatus.Settled)
            {
                return await _idempotency
                    .GetResultAsync<PaymentConfirmationResult>(
                        idempotencyKey, ct);
            }

            if (webhook.Status == "SUCCESS")
            {
                session.Status = PaymentStatus.Settled;
                session.SettlementDate = DateTime.UtcNow;
                session.GatewayRef = webhook.PaymentRef;

                var premiumTxn = new PremiumTransaction
                {
                    TransactionId = Guid.NewGuid(),
                    PolicyId = session.PolicyId,
                    TransactionType = "RENEWAL_PREMIUM",
                    Amount = session.Amount,
                    GstAmount = CalculateGST(session.Amount),
                    TotalAmount = session.Amount,
                    PaymentMode = session.PaymentMode,
                    PaymentGatewayRef = webhook.PaymentRef,
                    Status = "SETTLED",
                    TransactionDate = DateTime.UtcNow,
                    SettlementDate = DateTime.UtcNow
                };

                await _txnRepo.AddAsync(premiumTxn, ct);

                await _events.PublishAsync(new PremiumPaidEvent
                {
                    PolicyId = session.PolicyId,
                    Amount = session.Amount,
                    TransactionId = premiumTxn.TransactionId
                }, ct);
            }
            else
            {
                session.Status = PaymentStatus.Failed;
                session.FailureReason = webhook.FailureReason;
            }

            await _txnRepo.UpdateAsync(session, ct);
            await txn.CommitAsync(ct);

            var result = new PaymentConfirmationResult
            {
                Success = webhook.Status == "SUCCESS",
                TransactionId = premiumTxn?.TransactionId
            };

            await _idempotency.StoreAsync(
                idempotencyKey, result, ct);

            return result;
        }
        catch
        {
            await txn.RollbackAsync(ct);
            throw;
        }
    }

    private decimal CalculateNextDueAmount(Policy policy)
    {
        return policy.TotalPremium;
    }

    private decimal CalculateGST(decimal amount)
    {
        return Math.Round(amount * 0.18m, 2);
    }
}

The idempotency pattern is critical for payment processing because webhooks from payment gateways may be delivered multiple times due to network retries. The system must handle duplicate webhook deliveries without creating duplicate premium transactions. The implementation uses a database-level idempotency check within a serializable transaction to guarantee exactly-once processing even under concurrent webhook delivery.

13. Policy Loan & Surrender

Policy loans and surrenders are two of the most financially sensitive operations in the insurance platform. A policy loan allows the policyholder to borrow against the surrender value of their policy, typically up to 90% of the surrender value. Surrender is the premature termination of the policy where the policyholder receives the surrender value in exchange for giving up the policy benefits. Both operations require precise calculation of the surrender value based on actuarial factors that vary by product, policy year, and premium payment history.

Surrender Value Calculation

The guaranteed surrender value is typically calculated as a percentage of the total premiums paid, minus the cost of any benefits already enjoyed (such as mortality charges for term coverage). The formula varies by product and regulatory guidelines. For Indian life insurance products, IRDAI mandates a minimum guaranteed surrender value of 30% of total premiums paid (excluding the first year premium and any rider premiums) for policies that have been in force for at least 3 years. Special surrender value, if declared, may be higher and is based on the actuarial valuation of the policy's reserves.

C#
public class SurrenderService
{
    private readonly IPolicyRepository _policyRepo;
    private readonly IPremiumRepository _premiumRepo;
    private readonly IProductRepository _productRepo;

    public async Task<SurrenderQuote> CalculateSurrenderValueAsync(
        string policyNumber,
        CancellationToken ct)
    {
        var policy = await _policyRepo
            .GetByNumberAsync(policyNumber, ct);

        ValidateSurrenderEligibility(policy);

        var product = await _productRepo
            .GetActiveVersionAsync(policy.ProductCode, ct);

        var totalPremiumsPaid = await _premiumRepo
            .GetTotalPaidAsync(policy.PolicyId, ct);

        var yearsInForce = CalculateYearsInForce(policy.InceptionDate);
        var yearsPaid = CountPremiumYearsPaid(policy);

        var guaranteedSurrenderValue = CalculateGSV(
            totalPremiumsPaid,
            policy.BasePremium,
            policy.SumAssured,
            yearsPaid,
            product.Surrender);

        var specialSurrenderValue = await CalculateSSVAsync(
            policy, product, ct);

        var applicableSurrenderValue = Math.Max(
            guaranteedSurrenderValue,
            specialSurrenderValue);

        var outstandingLoan = await GetOutstandingLoanAsync(
            policy.PolicyId, ct);

        var netPayable = applicableSurrenderValue - outstandingLoan;

        return new SurrenderQuote
        {
            PolicyNumber = policyNumber,
            TotalPremiumsPaid = totalPremiumsPaid,
            GuaranteedSurrenderValue = guaranteedSurrenderValue,
            SpecialSurrenderValue = specialSurrenderValue,
            ApplicableSurrenderValue = applicableSurrenderValue,
            OutstandingLoan = outstandingLoan,
            NetPayableAmount = Math.Max(netPayable, 0),
            QuoteValidUntil = DateTime.UtcNow.AddDays(15),
            SurrenderPenalty = applicableSurrenderValue - totalPremiumsPaid
        };
    }

    private decimal CalculateGSV(
        decimal totalPaid,
        decimal basePremium,
        decimal sumAssured,
        int yearsPaid,
        SurrenderFactors factors)
    {
        if (yearsPaid < factors.MinPaidYearsForSurrender)
        {
            throw new SurrenderNotAllowedException(
                $"Minimum {factors.MinPaidYearsForSurrender} " +
                $"years required");
        }

        var firstYearExcluded = basePremium;
        var remainingPremiums = totalPaid - firstYearExcluded;

        var factor = factors.YearWiseFactors
            .Where(f => f.Year == Math.Min(yearsPaid, 20))
            .Select(f => f.Factor)
            .FirstOrDefault(factors.FirstYearSurrenderFactor);

        var gsv = remainingPremiums * factor;

        var minGSV = totalPaid * 0.30m;

        return Math.Max(gsv, minGSV);
    }

    private void ValidateSurrenderEligibility(Policy policy)
    {
        if (policy.PolicyStatus == PolicyStatus.Lapsed)
            throw new SurrenderNotAllowedException(
                "Lapsed policy cannot be surrendered directly");

        if (policy.PolicyStatus == PolicyStatus.Surrendered)
            throw new SurrenderNotAllowedException(
                "Policy already surrendered");

        if (policy.PolicyStatus == PolicyStatus.Claimed)
            throw new SurrenderNotAllowedException(
                "Claimed policy cannot be surrendered");
    }
}

14. Claim Settlement Process

Claim settlement is the ultimate purpose of life insurance — it is when the insurer fulfills its promise to the policyholder's family. The claim process must be handled with extreme care because it involves grieving families, large financial amounts, and strict regulatory timelines. IRDAI mandates that death claims must be settled within 30 days of receiving all required documents, or within 120 days if investigation is required.

Claim Settlement Workflow

flowchart TD A[Claim Filed] --> B[Document Verification] B --> C{All Documents Complete?} C -->|No| D[Request Missing Documents] D --> B C -->|Yes| E[Policy Status Verification] E --> F{Policy Active?} F -->|No| G[Reject Claim] F -->|Yes| H[Medical Investigation] H --> I{Suspicious?} I -->|No| J[Approve Claim] I -->|Yes| K[Investigation] K --> L{Fraud Detected?} L -->|Yes| G L -->|No| J J --> M[Calculate Settlement Amount] M --> N[Settlement Approval] N --> O[NEFT/RTGS Transfer] O --> P[Notify Beneficiary] P --> Q[Close Claim]
C#
public class ClaimService : IClaimService
{
    private readonly IClaimRepository _claimRepo;
    private readonly IPolicyRepository _policyRepo;
    private readonly IBeneficiaryRepository _beneficiaryRepo;
    private readonly IDocumentVaultService _docVault;
    private readonly IPaymentService _paymentService;
    private readonly IEventPublisher _events;

    public async Task<ClaimFilingResult> FileClaimAsync(
        FileClaimRequest request,
        CancellationToken ct)
    {
        var policy = await _policyRepo
            .GetByNumberAsync(request.PolicyNumber, ct);

        ValidateClaimFiling(policy, request.ClaimType);

        var claim = new Claim
        {
            ClaimId = Guid.NewGuid(),
            ClaimNumber = await GenerateClaimNumberAsync(ct),
            PolicyId = policy.PolicyId,
            ClaimType = request.ClaimType,
            ClaimStatus = ClaimStatus.Filed,
            FilingDate = DateOnly.FromDateTime(DateTime.UtcNow),
            CauseOfDeath = request.CauseOfDeath,
            HospitalDetails = request.HospitalDetails,
            CreatedAt = DateTime.UtcNow
        };

        await _claimRepo.AddAsync(claim, ct);

        await _events.PublishAsync(new ClaimFiledEvent
        {
            ClaimNumber = claim.ClaimNumber,
            PolicyNumber = request.PolicyNumber,
            ClaimType = request.ClaimType
        }, ct);

        return new ClaimFilingResult
        {
            ClaimNumber = claim.ClaimNumber,
            RequiredDocuments = GetRequiredDocuments(
                request.ClaimType),
            FiledAt = DateTime.UtcNow
        };
    }

    public async Task<ClaimSettlementResult> SettleClaimAsync(
        string claimNumber,
        SettlementApproval approval,
        CancellationToken ct)
    {
        var claim = await _claimRepo
            .GetByNumberAsync(claimNumber, ct);

        if (claim.ClaimStatus != ClaimStatus.Approved)
            throw new InvalidClaimStatusException(
                claim.ClaimStatus, ClaimStatus.Approved);

        var policy = await _policyRepo
            .GetByIdAsync(claim.PolicyId, ct);

        var beneficiaries = await _beneficiaryRepo
            .GetByPolicyAsync(policy.PolicyId, ct);

        var settlementAmount = approval.SettlementAmount;

        var payments = new List<BeneficiaryPayment>();
        foreach (var beneficiary in beneficiaries)
        {
            var shareAmount = settlementAmount *
                (beneficiary.SharePercentage / 100m);

            payments.Add(new BeneficiaryPayment
            {
                BeneficiaryId = beneficiary.BeneficiaryId,
                Name = beneficiary.Name,
                BankAccount = await GetBankAccountAsync(
                    beneficiary.BeneficiaryId, ct),
                Amount = shareAmount
            });
        }

        foreach (var payment in payments)
        {
            await _paymentService.InitiateNEFTAsync(
                new NEFTRequest
                {
                    BeneficiaryAccount = payment.BankAccount,
                    Amount = payment.Amount,
                    Reference = claim.ClaimNumber,
                    Remarks = $"Claim settlement - {claim.ClaimNumber}"
                }, ct);
        }

        claim.ClaimStatus = ClaimStatus.Paid;
        claim.SettlementAmount = settlementAmount;
        claim.PaymentDate = DateOnly.FromDateTime(DateTime.UtcNow);
        await _claimRepo.UpdateAsync(claim, ct);

        policy.PolicyStatus = PolicyStatus.Claimed;
        await _policyRepo.UpdateAsync(policy, ct);

        await _events.PublishAsync(new ClaimSettledEvent
        {
            ClaimNumber = claim.ClaimNumber,
            SettlementAmount = settlementAmount,
            BeneficiaryCount = payments.Count
        }, ct);

        return new ClaimSettlementResult
        {
            ClaimNumber = claim.ClaimNumber,
            TotalSettlementAmount = settlementAmount,
            BeneficiaryPayments = payments,
            SettledAt = DateTime.UtcNow
        };
    }
}
Fraud Detection: Claims filed within 2 years of policy inception (the contestability period) receive enhanced scrutiny. The system must flag claims that exhibit suspicious patterns: claims filed immediately after a policy revival, claims with inconsistent cause-of-death information, and claims where the beneficiary is not a family member. IRDAI requires insurers to investigate suspicious claims without unnecessarily delaying genuine settlements.

15. Beneficiary Management

Beneficiary management involves handling nominations, their shares, changes to nominations over the policy lifetime, and the legal complexities of claim distribution. Indian insurance law (Section 39 of the Insurance Act, 1938, as amended) allows policyholders to nominate one or more persons who shall receive the policy proceeds upon death. The nominee can be changed at any time during the policy lifetime by the policyholder, and the change takes effect upon communication to the insurer.

C#
public class BeneficiaryService
{
    private readonly IBeneficiaryRepository _beneficiaryRepo;
    private readonly IPolicyRepository _policyRepo;
    private readonly IEventPublisher _events;

    public async Task<BeneficiaryUpdateResult> UpdateNominationAsync(
        string policyNumber,
        UpdateNominationRequest request,
        CancellationToken ct)
    {
        var policy = await _policyRepo
            .GetByNumberAsync(policyNumber, ct);

        if (policy.PolicyStatus == PolicyStatus.Claimed)
            throw new CannotUpdateNominationException(
                "Cannot update nomination on claimed policy");

        var totalShare = request.Beneficiaries
            .Sum(b => b.SharePercentage);

        if (Math.Abs(totalShare - 100m) > 0.01m)
            throw new InvalidSharePercentageException(
                totalShare);

        await using var txn = await _beneficiaryRepo
            .BeginTransactionAsync(IsolationLevel.Serializable);

        try
        {
            var existing = await _beneficiaryRepo
                .GetByPolicyAsync(policy.PolicyId, ct);

            foreach (var ben in existing)
            {
                ben.IsDeleted = true;
                ben.UpdatedAt = DateTime.UtcNow;
            }
            await _beneficiaryRepo.UpdateRangeAsync(existing, ct);

            var newBeneficiaries = request.Beneficiaries.Select(b =>
                new Beneficiary
                {
                    BeneficiaryId = Guid.NewGuid(),
                    PolicyId = policy.PolicyId,
                    Name = b.Name,
                    Relationship = b.Relationship,
                    DateOfBirth = b.DateOfBirth,
                    SharePercentage = b.SharePercentage,
                    AadhaarHash = HashAadhaar(b.AadhaarNumber),
                    IsPrimary = b.IsPrimary,
                    CreatedAt = DateTime.UtcNow
                }).ToList();

            await _beneficiaryRepo.AddRangeAsync(
                newBeneficiaries, ct);

            await txn.CommitAsync(ct);

            await _events.PublishAsync(new NominationUpdatedEvent
            {
                PolicyNumber = policyNumber,
                BeneficiaryCount = newBeneficiaries.Count,
                UpdatedAt = DateTime.UtcNow
            }, ct);

            return new BeneficiaryUpdateResult
            {
                PolicyNumber = policyNumber,
                Beneficiaries = newBeneficiaries,
                UpdatedAt = DateTime.UtcNow
            };
        }
        catch
        {
            await txn.RollbackAsync(ct);
            throw;
        }
    }
}

16. Agent Management System

Insurance agents remain the primary distribution channel for life insurance products globally. In India, over 90% of life insurance policies are sold through agents. A comprehensive agent management system must handle agent recruitment and onboarding, commission calculation and disbursement, lead management, training and certification tracking, performance monitoring, and compliance with agency regulations.

Commission structures in life insurance are complex and regulated by IRDAI. First-year commission (FYC) is typically 25-40% of the first year premium, depending on the product type. Renewal commission (RYC) is 5-7.5% of renewal premiums for the duration the policy remains in force. The system must correctly calculate commissions considering clawback provisions (if the policy lapses within a specified period, the agent must return a portion of the FYC), vesting rules (commissions vest gradually over the first 3-5 years), and TDS deductions.

C#
public class CommissionService
{
    private readonly ICommissionRepository _commissionRepo;
    private readonly IAgentRepository _agentRepo;
    private readonly IProductRepository _productRepo;

    public async Task<CommissionCalculationResult>
        CalculateCommissionAsync(
            PremiumPaidEvent premiumEvent,
            CancellationToken ct)
    {
        var policy = await _policyRepo
            .GetByIdAsync(premiumEvent.PolicyId, ct);
        var agent = await _agentRepo
            .GetByIdAsync(policy.AgentId, ct);
        var product = await _productRepo
            .GetActiveVersionAsync(policy.ProductCode, ct);

        var yearsPaid = await _premiumRepo
            .GetYearsPaidCountAsync(policy.PolicyId, ct);

        var premiumYear = premiumEvent.IsFirstPremium ? 1 : yearsPaid;

        var commissionRate = premiumYear switch
        {
            1 => agent.CommissionRate,
            2 => agent.CommissionRate * 0.75m,
            3 => agent.CommissionRate * 0.50m,
            _ when premiumYear <= 5 => agent.CommissionRate * 0.25m,
            _ => product.ProductType == ProductType.ULIP
                ? 0.01m
                : 0.05m
        };

        var commissionAmount = premiumEvent.Amount * commissionRate;
        var tdsAmount = commissionAmount * 0.05m;
        var netCommission = commissionAmount - tdsAmount;

        if (policy.PolicyStatus == PolicyStatus.Lapsed &&
            premiumYear <= 3)
        {
            var clawbackRate = premiumYear switch
            {
                1 => 1.0m,
                2 => 0.75m,
                3 => 0.50m,
                _ => 0m
            };

            var clawbackAmount = commissionAmount * clawbackRate;

            return new CommissionCalculationResult
            {
                AgentId = agent.AgentId,
                PolicyNumber = policy.PolicyNumber,
                GrossCommission = commissionAmount,
                TdsDeduction = tdsAmount,
                NetCommission = netCommission,
                IsClawback = true,
                ClawbackAmount = clawbackAmount,
                EffectiveNet = netCommission - clawbackAmount
            };
        }

        var commission = new CommissionRecord
        {
            AgentId = agent.AgentId,
            PolicyId = policy.PolicyId,
            PremiumYear = premiumYear,
            GrossAmount = commissionAmount,
            TdsAmount = tdsAmount,
            NetAmount = netCommission,
            Status = CommissionStatus.Accrued,
            CalculatedAt = DateTime.UtcNow
        };

        await _commissionRepo.AddAsync(commission, ct);

        return new CommissionCalculationResult
        {
            AgentId = agent.AgentId,
            PolicyNumber = policy.PolicyNumber,
            GrossCommission = commissionAmount,
            TdsDeduction = tdsAmount,
            NetCommission = netCommission,
            IsClawback = false
        };
    }
}

17. Customer Portal & Self-Service

The customer portal is the digital front door to the insurance platform. It must provide a comprehensive self-service experience that reduces the need for customer service calls while maintaining security for sensitive financial operations. The portal must allow customers to view all their policies, download premium receipts and policy documents, initiate premium payments, file and track claims, update contact details, manage nominees, and apply for policy loans.

Security is paramount in the customer portal because it exposes financial data and enables transactions. Multi-factor authentication (MFA) is mandatory for all transactional operations. View-only operations (policy status, premium schedule) may use session-based authentication after initial login. Transactional operations (payments, surrender, claim filing) require re-authentication using OTP sent to the registered mobile number. Session tokens must expire after 15 minutes of inactivity for the web portal and 5 minutes for the mobile app.

The portal must also support accessibility requirements (WCAG 2.1 AA compliance) and be fully functional on low-bandwidth mobile connections common in tier-2 and tier-3 Indian cities. This means implementing progressive web app (PWA) patterns with offline capability for viewing cached policy data, lazy loading of images and documents, and compressed API responses using Brotli encoding.

Portal Features Matrix

FeatureAuth RequiredMFA RequiredAvailable On
View policy statusLoginNoWeb, Mobile
Download policy bondLoginNoWeb, Mobile
Download premium receiptLoginNoWeb, Mobile
Pay premiumLoginYes (OTP)Web, Mobile
Update contact detailsLoginYes (OTP)Web, Mobile
Change nomineeLoginYes (OTP)Web, Mobile
File death claimLoginYes (OTP)Web, Mobile
Apply for policy loanLoginYes (OTP + MFA)Web only
Request surrenderLoginYes (OTP + MFA)Web only
Revive lapsed policyLoginYes (OTP)Web, Mobile

18. Document Vault

The document vault handles storage, retrieval, and lifecycle management of all documents generated and consumed by the insurance platform. This includes policy bonds, premium receipts, claim forms, medical reports, KYC documents, endorsement letters, maturity certificates, and regulatory filings. Documents must be stored with versioning, access control, audit trails, and compliance with IRDAI data retention requirements.

Storage architecture uses a tiered approach: hot storage (SSD-backed object storage) for documents accessed within the last 2 years, warm storage (standard object storage) for documents accessed within the last 5 years, and cold storage (glacier or archive class) for older documents. All documents are encrypted at rest using AES-256 with customer-specific encryption keys. Document checksums are verified on every retrieval to detect corruption.

C#
public class DocumentVaultService : IDocumentVaultService
{
    private readonly IBlobStorage _blobStorage;
    private readonly IDocumentRepository _docRepo;
    private readonly IEncryptionService _encryption;
    private readonly IAuditLogger _auditLogger;

    public async Task<DocumentReference> UploadAsync(
        DocumentUpload upload,
        CancellationToken ct)
    {
        var checksum = ComputeSHA256(upload.Content);

        var encryptedContent = await _encryption.EncryptAsync(
            upload.Content,
            GetCustomerKey(upload.EntityId),
            ct);

        var storageKey = GenerateStorageKey(
            upload.EntityType,
            upload.EntityId,
            upload.DocumentType,
            upload.FileName);

        await _blobStorage.UploadAsync(
            storageKey,
            encryptedContent,
            upload.ContentType,
            ct);

        var document = new DocumentRecord
        {
            DocumentId = Guid.NewGuid(),
            EntityType = upload.EntityType,
            EntityId = upload.EntityId,
            DocumentType = upload.DocumentType,
            FileName = upload.FileName,
            FileSize = upload.Content.Length,
            MimeType = upload.ContentType,
            StorageKey = storageKey,
            ChecksumSha256 = checksum,
            UploadedBy = upload.UploadedBy,
            CreatedAt = DateTime.UtcNow
        };

        await _docRepo.AddAsync(document, ct);

        await _auditLogger.LogAsync(new AuditEntry
        {
            Action = "DOCUMENT_UPLOADED",
            EntityType = upload.EntityType,
            EntityId = upload.EntityId,
            Details = new { document.DocumentId, upload.FileName }
        }, ct);

        return new DocumentReference
        {
            DocumentId = document.DocumentId,
            FileName = upload.FileName,
            UploadedAt = document.CreatedAt
        };
    }

    public async Task<Stream> DownloadAsync(
        Guid documentId,
        string requestedBy,
        CancellationToken ct)
    {
        var document = await _docRepo.GetByIdAsync(documentId, ct);

        var content = await _blobStorage.DownloadAsync(
            document.StorageKey, ct);

        var decryptedContent = await _encryption.DecryptAsync(
            content,
            GetCustomerKey(document.EntityId),
            ct);

        var checksum = ComputeSHA256(decryptedContent);
        if (checksum != document.ChecksumSha256)
        {
            throw new DocumentCorruptionException(
                document.DocumentId);
        }

        await _auditLogger.LogAsync(new AuditEntry
        {
            Action = "DOCUMENT_DOWNLOADED",
            EntityType = document.EntityType,
            EntityId = document.EntityId,
            UserId = requestedBy,
            Details = new { document.DocumentId }
        }, ct);

        return new MemoryStream(decryptedContent);
    }
}

19. Regulatory Compliance (IRDAI)

IRDAI compliance is not optional — it is a fundamental architectural constraint that must be designed into every subsystem. The platform must generate regulatory filings automatically, maintain auditable records for every transaction, enforce product approval workflows, and implement IRDAI-mandated customer protection features like free-look cancellation periods and guaranteed surrender values.

Key IRDAI Regulations

RegulationRequirementSystem Implementation
Free Look Period15-30 days to cancel policy after issuanceAutomatic free-look tracking, no-questions-asked cancellation
Claim SettlementWithin 30 days (simple) or 120 days (investigated)SLA tracking, escalation alerts, auto-penalty calculation
Solvency MarginMinimum 150% solvency ratioReal-time solvency dashboard, reserve calculations
Policy Loan InterestMaximum rate prescribed by IRDAIConfigurable interest rates per regulatory directive
Premium Payment Grace30 days grace period for annual/semi-annualGrace period calculation, lapse prevention alerts
Mis-selling PreventionNeeds-based analysis before saleMandatory need analysis form in agent workflow
Data LocalizationAll data within Indian bordersIndia-only cloud regions, no cross-border data transfer
Annual StatementsPolicy statement to every policyholder annuallyBatch job for annual statement generation and delivery

Automated Regulatory Reporting

C#
public class RegulatoryReportingService
{
    private readonly IPolicyRepository _policyRepo;
    private readonly IClaimRepository _claimRepo;
    private readonly IPremiumRepository _premiumRepo;
    private readonly IIRDAILFilingService _filingService;

    public async Task<QuarterlyReturnResult>
        GenerateQuarterlyReturnAsync(
            int year, int quarter,
            CancellationToken ct)
    {
        var startDate = GetQuarterStart(year, quarter);
        var endDate = GetQuarterEnd(year, quarter);

        var newPoliciesCount = await _policyRepo
            .CountNewPoliciesAsync(startDate, endDate, ct);

        var totalSumAssured = await _policyRepo
            .GetTotalSumAssuredAsync(startDate, endDate, ct);

        var claimsFiled = await _claimRepo
            .CountClaimsAsync(startDate, endDate, ct);

        var claimsSettled = await _claimRepo
            .CountSettledClaimsAsync(startDate, endDate, ct);

        var claimsRepudiated = await _claimRepo
            .CountRepudiatedClaimsAsync(startDate, endDate, ct);

        var claimSettlementAmount = await _claimRepo
            .GetTotalSettlementAmountAsync(
                startDate, endDate, ct);

        var premiumsCollected = await _premiumRepo
            .GetTotalCollectedAsync(startDate, endDate, ct);

        var lapsedPolicies = await _policyRepo
            .CountLapsedPoliciesAsync(startDate, endDate, ct);

        var returnData = new QuarterlyReturn
        {
            ReportingPeriod = $"{year}-Q{quarter}",
            InsurerCode = "INS001",
            NewPoliciesIssued = newPoliciesCount,
            TotalSumAssured = totalSumAssured,
            ClaimsFiled = claimsFiled,
            ClaimsSettled = claimsSettled,
            ClaimsRepudiated = claimsRepudiated,
            ClaimSettlementAmount = claimSettlementAmount,
            PremiumsCollected = premiumsCollected,
            PoliciesLapsed = lapsedPolicies,
            ClaimSettlementRatio = claimsFiled > 0
                ? (decimal)claimsSettled / claimsFiled * 100
                : 100m,
            GeneratedAt = DateTime.UtcNow
        };

        await _filingService.SubmitQuarterlyReturnAsync(
            returnData, ct);

        return new QuarterlyReturnResult
        {
            ReturnData = returnData,
            SubmittedAt = DateTime.UtcNow
        };
    }
}

20. Multi-Channel Distribution

Life insurance products are sold through multiple channels: individual agents, bancassurance (bank partners), corporate agents, brokers, online direct (website and mobile app), and telemarketing. Each channel has different commission structures, lead management requirements, and reporting needs. The platform must support a unified product catalog and pricing engine while maintaining channel-specific workflows and commission calculations.

Bancassurance partnerships require API integrations with partner banks where bank employees can generate quotes and submit applications through the bank's own interface. This means the insurance platform must expose a partner API with appropriate authentication (OAuth 2.0 with partner-specific client IDs), rate limiting per partner, and white-label capabilities where the insurance branding is replaced with the bank's branding on generated documents.

Channel Comparison

ChannelCommissionLead SourceAPI RequirementsGrowth Rate
Individual Agent25-40% FYCAgent's networkAgent portal, mobile appStable
Bancassurance15-25% FYCBank customersPartner API, white-labelHigh
Corporate Agent15-30% FYCCorporate employeesGroup portal, bulk uploadMedium
Broker10-20% FYCBroker's clientsBroker API, comparison toolsMedium
Online Direct0% (no middleman)Digital marketingWeb, mobile appVery High
Telemarketing10-15% FYCPurchased leadsCall center integrationDeclining

The multi-channel architecture uses a channel abstraction layer that maps channel-specific operations to a common set of domain services. For example, a quote request from a bancassurance partner goes through the same QuoteEngine as a direct online request, but the channel context determines which product variants are available, which commission rates apply, and how the generated documents are branded.

21. Renewal & Revival

Renewal management is the process of collecting periodic premiums (quarterly, semi-annual, or monthly) after the first premium. The system must proactively remind customers of upcoming premiums, handle payment failures gracefully, manage grace periods, and process policy lapses when premiums remain unpaid beyond the grace period. Revival is the process of reinstating a lapsed policy by paying all overdue premiums along with interest and potentially providing evidence of continued insurability.

Renewal Timeline

gantt title Premium Payment Timeline dateFormat YYYY-MM-DD section Premium Cycle Due Date (Annual) :milestone, m1, 2026-07-01, 0d Grace Period (30 days) :crit, g1, 2026-07-01, 2026-07-31 Lapse Date :milestone, m2, 2026-07-31, 0d section Revival Window Revival Period (2 years) :rev1, 2026-07-31, 2028-07-31 Final Termination :milestone, m3, 2028-07-31, 0d
C#
public class RenewalService
{
    private readonly IPolicyRepository _policyRepo;
    private readonly INotificationService _notification;
    private readonly IPremiumScheduleRepository _scheduleRepo;

    public async Task ProcessRenewalBatchAsync(
        DateOnly processingDate,
        CancellationToken ct)
    {
        var duePolicies = await _policyRepo
            .GetPoliciesWithDuePremiumsAsync(processingDate, ct);

        foreach (var policy in duePolicies)
        {
            var schedule = await _scheduleRepo
                .GetNextDueScheduleAsync(
                    policy.PolicyId, processingDate, ct);

            if (schedule == null) continue;

            var daysSinceDue = processingDate
                .DayNumber - schedule.DueDate.DayNumber;

            switch (daysSinceDue)
            {
                case < 0:
                    await SendAdvanceReminderAsync(
                        policy, schedule, Math.Abs(daysSinceDue), ct);
                    break;
                case 0:
                    await SendDueNotificationAsync(
                        policy, schedule, ct);
                    break;
                case <= 30:
                    await SendGracePeriodReminderAsync(
                        policy, schedule, daysSinceDue, ct);
                    break;
                case 31:
                    await ProcessLapseAsync(
                        policy, schedule, ct);
                    break;
            }
        }
    }

    private async Task ProcessLapseAsync(
        Policy policy,
        PremiumSchedule schedule,
        CancellationToken ct)
    {
        policy.PolicyStatus = PolicyStatus.Lapsed;
        policy.RevivalDeadline = DateOnly.FromDateTime(
            DateTime.UtcNow.AddYears(2));
        policy.UpdatedAt = DateTime.UtcNow;
        policy.LastPremiumDate = schedule.PaidDate;

        await _policyRepo.UpdateAsync(policy, ct);

        await _notification.SendLapseNotificationAsync(
            policy, ct);
    }

    public async Task<RevivalResult> RevivePolicyAsync(
        string policyNumber,
        CancellationToken ct)
    {
        var policy = await _policyRepo
            .GetByNumberAsync(policyNumber, ct);

        if (policy.PolicyStatus != PolicyStatus.Lapsed)
            throw new InvalidPolicyStatusException(
                "Only lapsed policies can be revived");

        if (policy.RevivalDeadline < DateOnly.FromDateTime(
            DateTime.UtcNow))
            throw new RevivalDeadlineExpiredException(
                policy.RevivalDeadline);

        var overdueAmounts = await _scheduleRepo
            .GetOverdueAmountsAsync(policy.PolicyId, ct);

        var revivalCharge = overdueAmounts.TotalOverdue * 0.05m;
        var gstOnCharge = revivalCharge * 0.18m;
        var totalRevivalAmount = overdueAmounts.TotalOverdue +
            revivalCharge + gstOnCharge;

        return new RevivalResult
        {
            PolicyNumber = policyNumber,
            OverduePremiums = overdueAmounts.TotalOverdue,
            RevivalCharge = revivalCharge,
            GST = gstOnCharge,
            TotalAmount = totalRevivalAmount,
            PaymentDeadline = DateOnly.FromDateTime(
                DateTime.UtcNow.AddDays(15))
        };
    }
}

22. Analytics & Actuarial Reporting

The analytics platform must serve two distinct audiences: business analysts who need operational dashboards (daily premium collection, claim settlement ratios, agent performance) and actuaries who need deep statistical analysis (mortality experience studies, reserve adequacy testing, product profitability analysis). The data pipeline must maintain the integrity of financial data while providing real-time and batch analytics capabilities.

Key Metrics Dashboard

MetricDefinitionTargetAlert Threshold
Claim Settlement RatioClaims settled / Claims filed> 98%< 95%
Persistency Ratio (13th month)Policies still active after 13 months> 75%< 70%
Persistency Ratio (61st month)Policies still active after 61 months> 55%< 50%
Average Claim Settlement TimeDays from filing to payment< 15 days> 25 days
Premium Collection EfficiencyPremiums collected / Premiums due> 99%< 97%
Digital Adoption RateOnline policies / Total policies> 40%< 25%
Cost of AcquisitionTotal acquisition cost / New premium< 60%> 75%
Solvency RatioAvailable solvency / Required> 150%< 160%

Actuarial reporting requires a dedicated data warehouse separate from the operational database. The ETL pipeline extracts daily snapshots of policy data, premium transactions, and claim outcomes into a star schema optimized for actuarial queries. Mortality experience analysis requires joining policy data with claim data and computing age-specific death rates by product, gender, smoking status, and policy duration. These analyses directly inform future product pricing and reserve adequacy.

23. Cost Estimation

Monthly Cost Breakdown (AWS / Azure)

ComponentSpecificationMonthly Cost (USD)Notes
Application Servers8x m6i.xlarge (32 vCPU, 128GB)$4,480Auto-scaling group
PostgreSQL (RDS)db.r6g.2xlarge, Multi-AZ$2,800With read replicas
Redis Cluster3x cache.r6g.xlarge$1,800For quotes, sessions
MongoDBM10 cluster, 3 nodes$1,200Document metadata
S3 / Blob Storage5TB standard + 10TB IA$450Policy documents
Kafka (MSK)kafka.m5.2xlarge, 3 brokers$2,400Event streaming
Elasticsearch3x m6i.xlarge.search$1,500Logs, analytics
CDN (CloudFront)10TB transfer$850Web assets, documents
Load Balancer2x ALB$200Application load balancers
MonitoringDatadog / CloudWatch$600APM, logs, alerts
WAFAWS WAF$200API protection
Backup & DRCross-region replication$800Disaster recovery
Total$17,280

The infrastructure cost of approximately $17,000 per month supports 5 million active policies with 99.95% availability. The cost per policy per month is approximately $0.0034 or about ₹0.28, which is negligible compared to the average annual premium of ₹30,000-50,000 per policy. The most cost-effective optimization is aggressive caching of product configurations and premium calculations, which reduces database load by 60% and allows smaller database instances.

24. Testing Strategy

Actuarial Precision Testing

Testing a life insurance platform requires special attention to financial precision. Every premium calculation must match the expected value to the penny. Actuarial testing involves running the premium engine against known actuarial tables and expected results for hundreds of test cases across different products, ages, sum assured levels, and premium modes. Any deviation of more than ₹0.01 triggers an automatic failure.

C#
[TestClass]
public class QuoteEngineTests
{
    private QuoteEngine _engine;

    [TestInitialize]
    public void Setup()
    {
        _engine = new QuoteEngine(
            new MockMortalityTableService(),
            new MockProductRepository(),
            new MockBonusProjectionService());
    }

    [TestMethod]
    public async Task CalculateQuote_JeevanAnand_30Male_ShouldMatchActuarialTable()
    {
        var request = new QuoteRequest
        {
            ProductCode = "JEEVAN_ANAND_815",
            SumAssured = 5_000_000m,
            PolicyTerm = 25,
            PremiumTerm = 20,
            PremiumMode = "ANNUAL",
            DateOfBirth = new DateOnly(1996, 7, 13),
            Gender = "MALE",
            Smoker = false,
            Riders = new List<RiderSelection>(),
            State = "MAHARASHTRA"
        };

        var result = await _engine.CalculateQuoteAsync(
            request, CancellationToken.None);

        Assert.AreEqual(297450.00m, result.BasePremium,
            "Base premium must match actuarial table");
        Assert.AreEqual(370951.00m, result.TotalPayable,
            "Total payable including GST must be exact");
    }

    [TestMethod]
    public async Task CalculateQuote_ElderlyFemale_ShouldApplyCorrectMortalityRate()
    {
        var request = new QuoteRequest
        {
            ProductCode = "TERM_PLI_854",
            SumAssured = 1_000_000m,
            PolicyTerm = 10,
            PremiumTerm = 10,
            PremiumMode = "ANNUAL",
            DateOfBirth = new DateOnly(1966, 1, 15),
            Gender = "FEMALE",
            Smoker = false,
            Riders = new List<RiderSelection>(),
            State = "DELHI"
        };

        var result = await _engine.CalculateQuoteAsync(
            request, CancellationToken.None);

        Assert.IsTrue(result.BasePremium > 0,
            "Premium must be positive");
        Assert.IsTrue(result.TotalPayable < result.BasePremium * 1.2m,
            "GST should not exceed 20% of base");
    }

    [TestMethod]
    public async Task CalculateQuote_WithSmoker_ShouldApplySmokerLoading()
    {
        var nonSmokerRequest = CreateBaseRequest(smoker: false);
        var smokerRequest = CreateBaseRequest(smoker: true);

        var nonSmokerResult = await _engine.CalculateQuoteAsync(
            nonSmokerRequest, CancellationToken.None);
        var smokerResult = await _engine.CalculateQuoteAsync(
            smokerRequest, CancellationToken.None);

        Assert.IsTrue(smokerResult.BasePremium >
            nonSmokerResult.BasePremium,
            "Smoker premium must be higher than non-smoker");

        var loadingFactor = smokerResult.BasePremium /
            nonSmokerResult.BasePremium;
        Assert.IsTrue(loadingFactor >= 1.2m &&
            loadingFactor <= 1.5m,
            "Smoker loading should be between 20% and 50%");
    }

    [TestMethod]
    [ExpectedException(typeof(EligibilityException))]
    public async Task CalculateQuote_Underage_ShouldReject()
    {
        var request = new QuoteRequest
        {
            ProductCode = "JEEVAN_ANAND_815",
            DateOfBirth = DateOnly.FromDateTime(
                DateTime.Today.AddYears(-16)),
        };

        await _engine.CalculateQuoteAsync(
            request, CancellationToken.None);
    }

    [TestMethod]
    public async Task CalculateQuote_PremiumModes_ShouldMatchModalFactors()
    {
        var baseRequest = CreateBaseRequest();
        var results = new Dictionary<string, decimal>();

        foreach (var mode in new[] {
            "ANNUAL", "SEMI_ANNUAL", "QUARTERLY", "MONTHLY" })
        {
            var request = baseRequest with
                { PremiumMode = mode };
            var result = await _engine.CalculateQuoteAsync(
                request, CancellationToken.None);
            results[mode] = result.TotalPayable;
        }

        Assert.IsTrue(results["ANNUAL"] <
            results["SEMI_ANNUAL"] * 2.1m,
            "Annual should be cheaper than 2x semi-annual");
        Assert.IsTrue(results["SEMI_ANNUAL"] <
            results["QUARTERLY"] * 2.1m,
            "Semi-annual should be cheaper than 2x quarterly");
    }
}

Integration Test Coverage

Test CategoryScopeTarget CoverageKey Scenarios
Unit TestsPremium calculation, state machine90%All product types, edge cases
Integration TestsService-to-service80%End-to-end policy lifecycle
Actuarial TestsPremium accuracy100%1000+ test cases per product
Payment TestsPayment gateway100%All payment modes, failures, retries
Security TestsAuthentication, authorization100%MFA, session management, injection
Performance TestsLoad and stress testingN/APeak premium collection load
Compliance TestsIRDAI regulatory rules100%All regulatory constraints

25. Interview Q&A

Architecture & Design

Q1: How would you design the premium collection system to handle 1 million payments per day during peak season without data loss?

Answer: I would use a multi-layer approach. First, the API Gateway accepts payment initiation requests and persists them to a durable message queue (Kafka) before returning a session ID to the customer. This ensures that even if the payment service crashes after receiving the request, the payment intent is never lost. The payment service consumes from the Kafka topic, creates orders with the payment gateway, and persists the gateway order ID. When the payment gateway webhook arrives, it is processed idempotently against the persisted session. For reconciliation, a daily batch job compares the insurer's payment records with the gateway's settlement report to identify discrepancies. All financial records are written with serializable isolation to prevent double-crediting.

Q2: How would you handle the scenario where a policy was incorrectly priced due to a bug in the premium engine?

Answer: This is a critical scenario because insurance policies are legally binding contracts. The approach depends on the magnitude of the error and when it was discovered. For small rounding errors (a few paise), the insurer would typically honor the incorrectly calculated premium for the policy's lifetime as the cost of correction exceeds the error. For significant pricing errors, the insurer must follow IRDAI's dispute resolution process, which may involve adjusting the policy or offering a corrected premium with the policyholder's consent. The system must track the original quoted premium, any adjustments, and the reasoning for audit purposes. Implementing a versioned premium engine where the version used at policy inception is snapshot with the policy record ensures historical accuracy.

Q3: Design the claim settlement system to process claims within 30 days as mandated by IRDAI.

Answer: The key is designing a parallel processing pipeline rather than sequential. When a claim is filed, the system simultaneously initiates document verification, policy status verification, and beneficiary verification in parallel. A workflow engine (like Temporal or MassTransit) orchestrates the overall process, with escalation timers that trigger at day 15 (warning), day 25 (critical alert to manager), and day 28 (executive escalation). If any step requires additional information from the claimant, the system pauses that branch of the workflow while continuing others. The settlement payment is pre-approved for amounts below the underwriter's threshold, enabling straight-through processing for straightforward claims. Complex claims requiring investigation follow a separate path with a 120-day timeline.

Q4: How would you ensure data consistency across the policy, premium, and claim services?

Answer: Strong consistency within each service boundary is achieved using serializable transactions in PostgreSQL. Cross-service consistency uses the saga pattern with compensating transactions. For example, when processing a claim settlement, the claim service first reserves the settlement amount against the policy's reserve, then initiates the payment, and finally confirms the settlement. If the payment fails, a compensating transaction releases the reserved amount. The saga state machine is persisted in the event store, enabling automatic retry and recovery. For scenarios requiring absolute consistency (like premium payment crediting), the policy and premium services can use the same PostgreSQL database with cross-service foreign key references, accepting the tighter coupling for financial accuracy.

System Design

Q5: How would you design the system to support 100 different insurance products with varying benefit structures?

Answer: Rather than building product-specific logic for each product, I would design a product configuration engine where the benefit structure, premium calculation rules, and surrender factors are all data-driven. Each product has a configuration document (stored in MongoDB or PostgreSQL JSONB column) that defines the benefit type, riders, loading factors, and calculation formulas. The premium engine interprets this configuration at runtime. For products with non-standard benefit structures (like ULIPs with fund switching), the configuration includes custom calculation hooks that invoke product-specific C# classes. This approach allows product managers to configure new products through an admin interface, while developers only need to write code for genuinely new calculation patterns.

Q6: How would you handle the free-look period where a customer can cancel within 15-30 days?

Answer: When a policy is issued, the system records the issuance date and calculates the free-look end date (typically 15 days for online policies, 30 days for agent-sold policies as per IRDAI guidelines). During the free-look period, the policy status is FREE_LOOK rather than ACTIVE. If the customer initiates cancellation, the system refunds the entire premium paid minus a proportionate risk charge for the coverage period (typically the mortality charge for the days the policy was in force). The refund is processed to the original payment mode. If no cancellation request is received by the free-look end date, the system transitions the policy to ACTIVE status. The admin dashboard shows all policies currently in the free-look period for proactive customer engagement.

Q7: Design the system to handle policy revivals for millions of lapsed policies.

Answer: Revival is a batch-friendly operation because lapsed policies have a 2-year revival window and don't require real-time processing. The system maintains a revival queue (Kafka topic) that is populated when policies lapse. A revival processing service consumes from this queue and calculates revival amounts (overdue premiums + interest + GST). Customer outreach is automated: SMS and email reminders at 30, 90, 180, 365, and 730 days post-lapse. For self-service revival, the customer portal provides a revival calculator that shows the exact amount needed. The payment is processed through the same premium collection infrastructure. After the revival payment is confirmed, the policy status transitions from LAPSED back to ACTIVE, and the premium schedule is recalculated. Medical re-examination may be required for policies lapsed beyond 6 months, adding a workflow step before revival confirmation.

Q8: How would you design the document generation system to create thousands of policy bonds per day?

Answer: PDF generation is CPU-intensive, so I would use a distributed generation pipeline. Templates are stored in a versioned template repository and cached in Redis. The generation service pulls policy data from the policy service, merges it with the template, and produces the PDF using a library like QuestPDF or iTextSharp. To handle peak loads, I would use a worker pool with auto-scaling: a Kafka topic receives generation requests, and worker pods consume and process them in parallel. For efficiency, frequently used template sections are pre-rendered and cached. The generated PDFs are immediately uploaded to S3 and the document reference is stored in the document vault. The worker pods can scale from 2 to 50 instances based on queue depth, handling burst loads of 10,000 PDFs per hour.

Interview Tip: When discussing insurance system design, always emphasize the regulatory dimension. Interviewers at fintech companies want to see that you understand the constraints imposed by regulators like IRDAI, SEBI, and RBI. Mention specific regulations (Section 39 of the Insurance Act, IRDAI claim settlement guidelines, solvency requirements) to demonstrate domain knowledge.

© 2026 Ayodhyya. All rights reserved.

Design a Digital Life Insurance Platform (LIC-Style) — A Senior+ Guide