system-design60 min read

Design a Peer-to-Peer Lending Platform: The Complete Guide — A Senior+ Guide | Ayodhyya

Design a Peer-to-Peer Lending Platform

Building the modern lending marketplace: borrowers, investors, risk, escrow, compliance, and everything in between

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

Table of Contents

  1. Introduction — The P2P Lending Landscape
  2. P2P Lending Fundamentals
  3. Functional and Non-Functional Requirements
  4. Capacity Estimation and Back-of-Envelope
  5. Data Model and Storage Schema
  6. High-Level Architecture
  7. API Design
  8. Borrower Onboarding and KYC
  9. Credit Assessment and Risk Grading
  10. Loan Listing and Auction
  11. Investor Matching and Allocation
  12. Escrow and Fund Management
  13. Repayment Collection
  14. Default and Recovery
  15. Auto-Invest Rules Engine
  16. Secondary Market and Loan Trading
  17. Investor Dashboard
  18. Regulatory Compliance — RBI NBFC-P2P
  19. Fraud Prevention and Detection
  20. Documentation and E-Sign
  21. Notifications and Communication
  22. Analytics and Reporting
  23. Cost Estimation
  24. Testing Strategy
  25. Interview Q&A

1. Introduction — The P2P Lending Landscape

Peer-to-peer lending, commonly abbreviated as P2P lending, is a method of debt financing that allows individuals to borrow and lend money directly to one another without the intermediation of a traditional banking institution. The concept emerged in the early 2000s with platforms like Zopa in the United Kingdom, which launched in 2005, followed by Prosper and LendingClub in the United States. By 2025, the global P2P lending market had exceeded 200 billion dollars in cumulative originations, with platforms operating across more than 70 countries. In India specifically, the Reserve Bank of India introduced comprehensive regulatory guidelines for NBFC-P2P (Non-Banking Financial Company — Peer-to-Peer Lending) entities in 2017, establishing a formal framework for the operation of P2P lending platforms.

The fundamental value proposition of P2P lending is straightforward: it connects borrowers who need capital with investors who seek higher returns than traditional fixed-income instruments, while the platform itself provides the technology infrastructure, risk assessment, and regulatory compliance. For borrowers, P2P lending often offers lower interest rates than banks, faster approval times, and more flexible eligibility criteria. For investors, it offers returns that can range from 10 to 25 percent annually, significantly higher than savings accounts or government bonds. The platform earns its revenue by charging a processing fee to borrowers and a commission to investors on each successful loan disbursement.

Building a P2P lending platform is one of the most complex system design challenges in the fintech domain. Unlike simpler systems like URL shorteners or chat applications, a P2P lending platform must handle financial transactions with strict regulatory compliance, implement sophisticated credit assessment algorithms, manage escrow accounts for fund safety, support complex auction and matching mechanisms, provide real-time dashboards for both borrowers and investors, and ensure absolute data integrity because every single byte of data represents real money. A single bug in the repayment calculation engine could lead to millions of dollars in incorrect interest charges, and a failure in the escrow system could result in investor funds being misappropriated. The stakes are extraordinarily high.

Interview Context: The P2P lending platform design question tests your understanding of financial systems, payment processing, regulatory compliance, risk modeling, auction mechanisms, and building systems where correctness is non-negotiable. It appears frequently in senior and staff-level system design interviews at fintech companies and is considered one of the most challenging design questions because of the intersection of technology, finance, and regulation.

This comprehensive guide walks through the complete design of a P2P lending platform, from understanding the business domain to implementing the core services in C#. We will cover borrower onboarding and KYC verification, credit scoring and risk grading, loan listing and auction mechanisms, investor matching and allocation algorithms, escrow fund management, repayment collection with auto-debit, default management and recovery, auto-invest rule engines, secondary market for loan trading, regulatory compliance with RBI and NBFC-P2P guidelines, fraud prevention, document management with e-signatures, notification systems, analytics and reporting dashboards, cost estimation, and testing strategies. By the end of this guide, you will have a thorough understanding of what it takes to build a production-grade P2P lending platform that can handle thousands of loans and millions of dollars in transactions.

2. P2P Lending Fundamentals

Before diving into the system design, it is essential to understand the core concepts and terminology of P2P lending. A typical P2P lending transaction involves several parties: the borrower who requests a loan, the investors (also called lenders) who fund the loan, the P2P platform that facilitates the transaction, an escrow bank where investor funds are held, and potentially a recovery agent in case of default. The platform acts as an intermediary that does not lend its own money but rather connects borrowers with investors and provides the technology and compliance infrastructure.

When a borrower applies for a loan, the platform performs credit assessment, assigns a risk grade, and lists the loan on the marketplace. Investors then browse available loans and decide how much to invest. Once the loan is fully funded (or partially funded, depending on the platform rules), the funds are transferred from the escrow account to the borrower's bank account. The borrower then repays the loan in monthly installments (EMIs) that include both principal and interest. The platform distributes each EMI to the investors proportionally based on their investment in that loan.

Key Terminology

TermDefinitionExample
PrincipalThe original loan amount borrowed1,00,000 INR
EMIEquated Monthly Installment8,792 INR for 12 months at 12%
Risk GradeLetter or number grade indicating creditworthinessA+, A, B+, B, C+
YieldAnnualized return for the investor14% per annum
DefaultFailure to pay EMI for 90+ daysNPA (Non-Performing Asset)
EscrowHeld funds managed by the platform until disbursementInvestor wallet balance
Platform FeeCommission charged by the platform2% of loan amount from borrower
PrepaymentEarly repayment of loan before tenure endsFull early settlement
NPVNet Present Value of future cash flowsDiscounted at risk-adjusted rate
Charge-offLoan written off as unrecoverableAfter 180 days delinquent

P2P Lending Lifecycle

graph LR A[Borrower Registration] --> B[KYC Verification] B --> C[Credit Assessment] C --> D[Risk Grading] D --> E[Loan Listing] E --> F[Investor Bidding] F --> G[Fund Matching] G --> H[Escrow Hold] H --> I[Disbursement] I --> J[Repayment Schedule] J --> K[Monthly EMI Collection] K --> L[Investor Payout] K --> M[Default Handling] M --> N[Recovery Process]

Revenue Model

The platform generates revenue through multiple streams. The primary source is the processing fee charged to borrowers, typically 1 to 3 percent of the loan amount, deducted at the time of disbursement. The secondary revenue stream is the investor commission, usually 0.5 to 2 percent of the amount invested, collected monthly as a percentage of the EMI received. Additional revenue can come from late payment fees charged to borrowers, prepayment penalties, premium investor features such as advanced analytics and priority access to high-rated loans, and data services provided to institutional investors. The total cost of funds for a P2P platform typically ranges between 3 to 6 percent of total originations.

3. Functional and Non-Functional Requirements

Functional Requirements

#RequirementPriorityDetails
F1Borrower registration and KYCMustPAN, Aadhaar verification, income proof upload
F2Loan application and credit scoringMustAutomated risk grade assignment using bureau and alternative data
F3Loan listing and auctionMustBorrower posts loan request, investors bid with interest rate
F4Investor registration and KYCMustPAN verification, bank account linking, risk profiling
F5Escrow fund managementMustSegregated investor wallets, fund hold before disbursement
F6Loan disbursementMustNEFT/RTGS transfer to borrower bank account
F7Repayment collectionMustNACH auto-debit, UPI mandate, manual payment
F8Investor payout distributionMustPro-rata distribution of EMI to investors
F9Auto-invest rulesShouldRule-based automatic investment based on risk, tenure, rate
F10Secondary marketShouldInvestors can sell loan parts to other investors
F11Investor dashboardMustPortfolio overview, returns, defaults, cash flow projections
F12Default and recoveryMustAutomated dunning, recovery agent assignment, write-off
F13Document managementShouldLoan agreements, e-signatures, KYC document storage
F14Regulatory reportingMustRBI reporting, NBFC-P2P compliance, audit trail
F15NotificationsMustSMS, email, push notifications for all events
F16Fraud detectionMustIdentity fraud, income fraud, duplicate detection
F17Analytics and reportingShouldPlatform metrics, investor reports, regulatory dashboards

Non-Functional Requirements

RequirementTargetRationale
Availability99.95%Financial platform requires high uptime
LatencyP99 < 500msAPI response time for dashboard and lending operations
ConsistencyStrong consistencyFinancial data cannot have eventual consistency for balances
Throughput10,000 TPSPeak during batch repayment processing
Data Durability99.999999%Financial records must be permanently durable
SecuritySOC 2 Type IICompliance for financial data handling
Audit TrailImmutable logEvery mutation logged for regulatory audit
Data EncryptionAES-256 at rest, TLS 1.3 in transitPCI DSS and regulatory requirement

4. Capacity Estimation and Back-of-Envelope

Understanding the scale of operations is crucial for designing the infrastructure. Let us estimate the capacity requirements for a mid-sized P2P lending platform in India targeting 500,000 registered users with 200,000 active loans at any given time. These numbers represent a realistic scenario for a platform that has been operating for approximately two to three years and has achieved product-market fit. The capacity estimates drive infrastructure decisions from database sizing to API gateway configuration to batch processing window scheduling.

User and Transaction Estimates

MetricDailyMonthlyAnnual
New borrower registrations50015,000180,000
New investor registrations2006,00072,000
Loan applications3009,000108,000
Loan disbursements1504,50054,000
Active loans200,000--
Daily EMI collections8,000240,0002,880,000
Investment transactions2,00060,000720,000
Dashboard page views50,0001,500,00018,000,000

Storage Estimates

Each loan record occupies approximately 2 KB including metadata. With 54,000 new loans per year and a 36-month average tenure, the active loan data grows to approximately 200,000 records or 400 MB per year for the core loan table. Repayment records are smaller at 500 bytes each, generating roughly 1.44 million records per year or 720 MB. Investor portfolio data adds another 200 MB per year. KYC documents stored as PDFs average 2 MB each, with 252,000 new users per year requiring 504 GB of document storage annually. Audit logs are the largest contributor at approximately 50 GB per year assuming 100 million log entries. The total first-year data footprint including indexes, backups, and replication is approximately 2 TB, growing to 6 TB by the end of year three.

Bandwidth Estimates

Assuming 50,000 daily page views averaging 500 KB each, the read bandwidth for dashboards is approximately 25 GB per day or 290 KB per second. API calls for loan operations, at 20,000 requests per day averaging 2 KB each, contribute 40 MB per day. The batch repayment processing at 8,000 transactions per day generates approximately 16 MB of data. Total daily bandwidth is approximately 25 GB, which translates to a sustained throughput of roughly 300 KB per second with peaks of 5 MB per second during batch processing windows. Network capacity should be provisioned at 10x the peak to handle traffic spikes during repayment cycles at the start and end of each month.

Compute Estimates

The API layer requires approximately 4 CPU cores and 8 GB RAM per node, with 3 nodes for high availability. The credit scoring service needs GPU instances for the ML model inference, approximately 2 NVIDIA T4 instances running behind a load balancer. The batch repayment processor runs on a single dedicated instance with 16 CPU cores and 32 GB RAM, scheduled during off-peak hours (2 AM to 6 AM IST). The auto-invest engine runs continuously and requires 2 instances with 4 CPU cores and 8 GB RAM each. Redis clusters for caching need 3 nodes with 32 GB RAM each for a total of 96 GB cache capacity. The Elasticsearch cluster for search and audit logs requires 5 nodes with 16 CPU cores, 64 GB RAM, and 500 GB SSD storage each.

5. Data Model and Storage Schema

The data model for a P2P lending platform must capture the complete lifecycle of a loan from application to closure. We use a relational database (PostgreSQL) for transactional data due to the strong consistency requirements, and a separate document store for KYC files and loan agreement PDFs. The schema is designed around the principle of immutable event logging: instead of mutating records in place, we append new records and maintain a current-state view through materialized views or read-optimized tables.

Entity Relationship Overview

erDiagram BORROWER ||--o{ LOAN_REQUEST : creates INVESTOR ||--o{ INVESTMENT : makes LOAN_REQUEST ||--|| LOAN : converts_to LOAN ||--|{ EMI_SCHEDULE : generates LOAN ||--|{ INVESTMENT : funded_by LOAN_REQUEST ||--|| CREDIT_SCORE : assessed_by BORROWER ||--|| KYC_RECORD : verified_by INVESTOR ||--|| KYC_RECORD : verified_by INVESTMENT ||--o{ INVESTMENT_PAYOUT : receives LOAN ||--o{ DEFAULT_RECORD : may_have LOAN ||--o{ SECONDARY_MARKET_LISTING : tradeable

Core Tables

public class Borrower
{
    public Guid Id { get; set; }
    public string FullName { get; set; }
    public string Email { get; set; }
    public string Phone { get; set; }
    public string PanNumber { get; set; }
    public string AadhaarHash { get; set; }
    public decimal AnnualIncome { get; set; }
    public string EmploymentType { get; set; }
    public string EmployerName { get; set; }
    public BorrowerStatus Status { get; set; }
    public int CreditScore { get; set; }
    public DateTime CreatedAt { get; set; }
    public DateTime? KycVerifiedAt { get; set; }
    public string RiskGrade { get; set; }
}

public class LoanRequest
{
    public Guid Id { get; set; }
    public Guid BorrowerId { get; set; }
    public decimal RequestedAmount { get; set; }
    public decimal ApprovedAmount { get; set; }
    public int TenureMonths { get; set; }
    public decimal MaxInterestRate { get; set; }
    public string Purpose { get; set; }
    public string RiskGrade { get; set; }
    public LoanRequestStatus Status { get; set; }
    public DateTime CreatedAt { get; set; }
    public DateTime? ListedAt { get; set; }
    public DateTime? FullyFundedAt { get; set; }
    public DateTime? DisbursedAt { get; set; }
}

public class Loan
{
    public Guid Id { get; set; }
    public Guid LoanRequestId { get; set; }
    public Guid BorrowerId { get; set; }
    public decimal PrincipalAmount { get; set; }
    public decimal OutstandingPrincipal { get; set; }
    public decimal AnnualInterestRate { get; set; }
    public int TenureMonths { get; set; }
    public decimal MonthlyEmi { get; set; }
    public int PaidEmiCount { get; set; }
    public LoanStatus Status { get; set; }
    public DateTime DisbursedAt { get; set; }
    public DateTime MaturityDate { get; set; }
    public int DaysPastDue { get; set; }
    public decimal TotalRecoveryAmount { get; set; }
}

public class Investor
{
    public Guid Id { get; set; }
    public string FullName { get; set; }
    public string Email { get; set; }
    public string Phone { get; set; }
    public string PanNumber { get; set; }
    public decimal TotalInvested { get; set; }
    public decimal AvailableBalance { get; set; }
    public decimal TotalReturns { get; set; }
    public InvestorStatus Status { get; set; }
    public string RiskProfile { get; set; }
    public DateTime CreatedAt { get; set; }
    public DateTime? KycVerifiedAt { get; set; }
}

public class Investment
{
    public Guid Id { get; set; }
    public Guid InvestorId { get; set; }
    public Guid LoanId { get; set; }
    public decimal Amount { get; set; }
    public decimal InvestedAtRate { get; set; }
    public decimal ReturnsReceived { get; set; }
    public InvestmentStatus Status { get; set; }
    public DateTime CreatedAt { get; set; }
}

public class EmiSchedule
{
    public Guid Id { get; set; }
    public Guid LoanId { get; set; }
    public int EmiNumber { get; set; }
    public decimal PrincipalPart { get; set; }
    public decimal InterestPart { get; set; }
    public decimal TotalAmount { get; set; }
    public DateTime DueDate { get; set; }
    public DateTime? PaidDate { get; set; }
    public decimal? PaidAmount { get; set; }
    public EmiStatus Status { get; set; }
}

Supporting Tables

TablePurposeKey Columns
escrow_transactionsTrack all fund movementstransaction_id, loan_id, investor_id, amount, type, status
auto_invest_rulesInvestor auto-invest preferencesinvestor_id, min_amount, max_amount, risk_grades, min_rate
kyc_documentsKYC document metadatauser_id, document_type, file_path, verified_at, verified_by
loan_agreementsGenerated loan agreementsloan_id, version, file_path, signed_at, e_sign_id
audit_logsImmutable audit traillog_id, entity_type, entity_id, action, user_id, timestamp, metadata
recovery_recordsDefault recovery trackingloan_id, agent_id, amount_recovered, recovery_date, method
secondary_market_listingsLoan part listings for salelisting_id, loan_id, seller_id, units, price, status
notification_logsNotification delivery trackingnotification_id, user_id, channel, template, sent_at, delivered
credit_bureau_pullsCredit bureau data snapshotsborrower_id, bureau, score, pulled_at, raw_data_path
platform_configRuntime configurationconfig_key, config_value, updated_at, updated_by

Database Indexing Strategy

public class DatabaseIndexes
{
    public const string LoansByBorrower =
        "CREATE INDEX IX_loans_borrower_status ON loans(borrower_id, status);";

    public const string InvestmentsByInvestor =
        "CREATE INDEX IX_investments_investor_status ON investments(investor_id, status);";

    public const string EmiPendingByLoan =
        "CREATE INDEX IX_emi_pending ON emi_schedule(loan_id, status, due_date);";

    public const string ListedLoansByGrade =
        "CREATE INDEX IX_loan_requests_listed ON loan_requests(status, risk_grade, requested_amount);";

    public const string DefaultLoans =
        "CREATE INDEX IX_loans_dpd ON loans(days_past_due, status);";

    public const string EscrowByInvestor =
        "CREATE INDEX IX_escrow_investor_status ON escrow_transactions(investor_id, status);";
}
Data Integrity: All financial balances must be computed from the transaction log, not stored as mutable counters. Use double-entry bookkeeping where every debit has a corresponding credit. A reconciliation job must run every hour to verify that computed balances match stored balances, and discrepancies must trigger an immediate alert to the operations team.

6. High-Level Architecture

The P2P lending platform follows a microservices architecture with clear domain boundaries. Each service owns its data and communicates with other services through well-defined APIs and asynchronous event streams. The architecture prioritizes consistency for financial operations and availability for read-heavy dashboards. The separation of concerns ensures that a failure in the notification service does not affect loan disbursement, and a performance issue in the analytics pipeline does not impact repayment collection.

graph TB subgraph "Client Layer" WEB[Web Application React] MOB[Mobile App MAUI] ADMIN[Admin Panel] end subgraph "API Gateway" GW[API Gateway Ocelot] AUTH[Auth Service IdentityServer] RATE[Rate Limiter] end subgraph "Core Services" BOR[Borrower Service] INV[Investor Service] LN[Loan Service] CR[Credit Service] ESC[Escrow Service] REP[Repayment Service] AUCTION[Auction Service] end subgraph "Support Services" KYC[KYC Service] DOC[Document Service] NOTIFY[Notification Service] FRAUD[Fraud Detection] AUTO[Auto-Invest Engine] SEC[Secondary Market] end subgraph "Data Layer" PG[(PostgreSQL Primary)] PG_REP[(PostgreSQL Replica)] REDIS[(Redis Cache)] ES[(Elasticsearch)] S3[(Object Storage S3)] KAFKA[Kafka Event Bus] end subgraph "External" BUREAU[Credit Bureau CIBIL] BANK[Banking Gateway NEFT/RTGS] NACH[NACH Auto-Debit] UPI[UPI Gateway] DSC[DSC E-Sign Provider] end WEB --> GW MOB --> GW ADMIN --> GW GW --> AUTH GW --> RATE GW --> BOR GW --> INV GW --> LN GW --> AUCTION GW --> ESC GW --> REP BOR --> PG INV --> PG LN --> PG CR --> PG ESC --> PG REP --> PG BOR --> REDIS INV --> REDIS LN --> REDIS LN --> KAFKA REP --> KAFKA ESC --> KAFKA CR --> BUREAU ESC --> BANK REP --> NACH REP --> UPI DOC --> DSC KYC --> S3 DOC --> S3

Service Responsibilities

ServiceResponsibilityConsistencyDatabase
Borrower ServiceRegistration, profile, KYC statusStrongPostgreSQL
Investor ServiceRegistration, portfolio, walletStrongPostgreSQL
Loan ServiceApplication, lifecycle, scheduleStrongPostgreSQL
Credit ServiceBureau pull, scoring, gradingEventualPostgreSQL + Cache
Escrow ServiceFund hold, release, reconciliationStrongPostgreSQL
Repayment ServiceEMI collection, distributionStrongPostgreSQL
Auction ServiceLoan listing, bidding, matchingStrongPostgreSQL + Redis
KYC ServiceIdentity verification, document checkEventualPostgreSQL + S3
Document ServiceAgreement generation, e-signEventualPostgreSQL + S3
Notification ServiceSMS, email, push deliveryEventualPostgreSQL
Fraud DetectionRule engine, anomaly detectionEventualElasticsearch
Auto-Invest EngineAutomatic investment matchingEventualRedis + PostgreSQL
Secondary MarketLoan part tradingStrongPostgreSQL

Event-Driven Communication

Cross-service communication happens through Kafka topics to ensure loose coupling and reliable delivery. Critical events include LoanDisbursed, EmiReceived, EmiOverdue, LoanDefaulted, and InvestmentPayout. Each event carries the complete context needed by consuming services, ensuring that services can be rebuilt from the event log if needed. All events are persisted for 30 days in Kafka and archived to cold storage for audit purposes. Dead letter queues capture events that fail processing after three retries, and an operations dashboard monitors DLQ depth to ensure no events are permanently lost.

Resilience Patterns

The architecture implements circuit breakers on all external integrations (credit bureau, banking gateway, NACH gateway) using Polly. If a circuit breaker trips, the system queues operations for retry rather than failing the user request immediately. The retry policy uses exponential backoff with jitter, starting at 1 second and capping at 30 seconds. For the escrow service, the circuit breaker has a higher threshold (5 failures vs 3) because financial operations must not be interrupted without careful consideration. Health checks run every 15 seconds and are aggregated into a dashboard that shows the real-time health of every service and its dependencies.

7. API Design

The P2P lending platform exposes a RESTful API with versioning. All endpoints require authentication via JWT tokens with a 15-minute expiry and refresh token rotation. Financial operations require additional authorization checks and are protected with idempotency keys to prevent duplicate transactions. The API follows a consistent response envelope with success, data, error, and requestId fields.

Core API Endpoints

MethodEndpointDescriptionAuth
POST/api/v1/borrowers/registerRegister new borrowerPublic
POST/api/v1/borrowers/kycSubmit KYC documentsBearer
GET/api/v1/borrowers/profileGet borrower profileBearer
POST/api/v1/loans/applyApply for a loanBearer
GET/api/v1/loans/{id}/scheduleGet EMI scheduleBearer
POST/api/v1/loans/{id}/prepayPrepay loanBearer
GET/api/v1/marketplace/loansBrowse available loansBearer
POST/api/v1/investmentsInvest in a loanBearer
GET/api/v1/investors/portfolioGet investment portfolioBearer
POST/api/v1/investors/auto-investSet auto-invest rulesBearer
POST/api/v1/escrow/depositDeposit funds to walletBearer
POST/api/v1/escrow/withdrawWithdraw available fundsBearer
POST/api/v1/repayments/payMake manual repaymentBearer
POST/api/v1/secondary-market/listList loan part for saleBearer
POST/api/v1/secondary-market/buyBuy listed loan partBearer

API Implementation Example

[ApiController]
[Route("api/v1/[controller]")]
[Authorize]
public class InvestmentsController : ControllerBase
{
    private readonly IInvestmentService _investmentService;
    private readonly IEscrowService _escrowService;
    private readonly ILogger<InvestmentsController> _logger;

    public InvestmentsController(
        IInvestmentService investmentService,
        IEscrowService escrowService,
        ILogger<InvestmentsController> logger)
    {
        _investmentService = investmentService;
        _escrowService = escrowService;
        _logger = logger;
    }

    [HttpPost]
    [Idempotent]
    public async Task<ActionResult<InvestmentResponse>> Invest(
        [FromBody] InvestRequest request)
    {
        var investorId = GetAuthenticatedInvestorId();

        var loanRequest = await _investmentService
            .GetLoanRequestAsync(request.LoanRequestId);

        if (loanRequest == null)
            return NotFound(new { error = "Loan request not found" });

        if (loanRequest.Status != LoanRequestStatus.Listed)
            return BadRequest(new { error = "Loan is not available" });

        if (request.Amount < loanRequest.MinInvestmentAmount)
            return BadRequest(new { error = "Below minimum investment" });

        if (request.Amount > loanRequest.RemainingFundingAmount)
            return BadRequest(new { error = "Amount exceeds remaining funding" });

        var balance = await _escrowService
            .GetAvailableBalanceAsync(investorId);

        if (balance < request.Amount)
            return BadRequest(new { error = "Insufficient wallet balance" });

        var investment = await _investmentService
            .PlaceInvestmentAsync(investorId, request.LoanRequestId, request.Amount);

        _logger.LogInformation(
            "Investment placed: Investor={InvestorId}, Loan={LoanId}, Amount={Amount}",
            investorId, request.LoanRequestId, request.Amount);

        return Ok(new InvestmentResponse
        {
            InvestmentId = investment.Id,
            LoanRequestId = investment.LoanRequestId,
            Amount = investment.Amount,
            ExpectedRate = investment.InvestedAtRate,
            Status = investment.Status.ToString(),
            CreatedAt = investment.CreatedAt
        });
    }
}

Rate Limiting Configuration

Endpoint GroupRate LimitWindowScope
Registration5 requests1 hourPer IP
KYC Submission10 requests1 dayPer user
Loan Application3 requests1 dayPer user
Investment100 requests1 minutePer user
Dashboard300 requests1 minutePer user
Escrow Deposit10 requests1 hourPer user

8. Borrower Onboarding and KYC

Know Your Customer (KYC) compliance is a regulatory requirement for all financial platforms in India. The RBI mandates that NBFC-P2P platforms verify the identity and address of all borrowers and investors before allowing them to participate in lending or borrowing. The KYC process must be completed within 30 days of registration, and the platform must maintain KYC records for at least 5 years after the closure of the business relationship. The KYC process must balance regulatory compliance with user experience, as overly complex verification flows lead to high dropout rates during onboarding.

KYC Process Flow

graph TD A[User Registers] --> B[Upload PAN Card] B --> C[Upload Aadhaar Front and Back] C --> D[Upload Selfie] D --> E[OCR Extraction] E --> F[Aadhaar Verification via UIDAI] F --> G[PAN Verification via NSDL] G --> H[Face Match Selfie vs Aadhaar] H --> I{All Checks Pass?} I -->|Yes| J[KYC Approved] I -->|No| K[Manual Review Queue] K --> L{Review Decision} L -->|Approved| J L -->|Rejected| M[KYC Failed - Notify User] J --> N[Enable Loan Application]

KYC Service Implementation

public class KycVerificationService
{
    private readonly IAadhaarService _aadhaarService;
    private readonly IPanService _panService;
    private readonly IFaceMatchService _faceMatchService;
    private readonly IOcrService _ocrService;
    private readonly IKycRepository _kycRepository;

    public async Task<KycResult> VerifyAsync(KycSubmission submission)
    {
        var result = new KycResult();

        var panData = await _ocrService.ExtractPanAsync(submission.PanImage);
        var aadhaarData = await _ocrService.ExtractAadhaarAsync(submission.AadhaarFrontImage);

        var panResult = await _panService.VerifyAsync(
            panData.PanNumber, submission.FullName, panData.DateOfBirth);

        result.PanVerified = panResult.IsValid;
        result.PanName = panData.NameOnCard;

        var aadhaarResult = await _aadhaarService.VerifyAsync(
            aadhaarData.UidNumber, submission.FullName);

        result.AadhaarVerified = aadhaarResult.IsValid;

        var faceResult = await _faceMatchService.CompareAsync(
            submission.SelfieImage, aadhaarData.Photo);

        result.FaceMatchScore = faceResult.Confidence;
        result.FaceMatchPassed = faceResult.Confidence > 0.85m;

        var isDuplicate = await _kycRepository
            .CheckDuplicateAsync(panData.PanNumber, aadhaarData.UidNumber);

        result.IsDuplicate = isDuplicate;

        if (result.PanVerified && result.AadhaarVerified && result.FaceMatchPassed && !result.IsDuplicate)
        {
            result.Status = KycStatus.Approved;
        }
        else if (result.PanVerified && result.AadhaarVerified && !result.FaceMatchPassed)
        {
            result.Status = KycStatus.ManualReview;
            result.ReviewReason = "Face match below threshold";
        }
        else
        {
            result.Status = KycStatus.Rejected;
        }

        await _kycRepository.SaveResultAsync(submission.UserId, result);
        return result;
    }
}

KYC Integration Partners

VerificationProviderLatencyCost per Check
Aadhaar eKYCUIDAI via NSDL1-3 seconds15 INR
PAN VerificationNSDL / Protean2-5 seconds10 INR
Bank Account VerificationDecentro / Setu1-2 seconds5 INR
Face MatchAmazon Rekognition1-2 seconds2 INR
Address VerificationDigilocker API2-4 seconds8 INR
Income VerificationBank Statement Analyzer3-5 seconds20 INR
User Experience Tip: The entire KYC flow must complete in under 5 minutes. Use parallel verification calls where possible (PAN and Aadhaar verification can run simultaneously). Show real-time progress to the user and provide clear error messages with specific remediation steps when verification fails. Implement a document re-upload flow that preserves previously verified data so users do not have to start from scratch.

9. Credit Assessment and Risk Grading

Credit assessment is the heart of a P2P lending platform. The platform must evaluate each borrower's creditworthiness accurately because the risk is borne entirely by the investors, not the platform. A poor credit assessment leads to high default rates, which drives investors away from the platform. The credit assessment process combines traditional credit bureau data with alternative data sources and machine learning models to produce a risk grade for each borrower.

Credit Scoring Model

The credit scoring model takes inputs from multiple sources and produces a score between 300 and 900. The key features used in the model include credit bureau score (weight 30%), debt-to-income ratio (weight 20%), employment stability (weight 15%), credit history length (weight 10%), utilization ratio (weight 10%), recent credit inquiries (weight 8%), and alternative data such as mobile phone usage patterns, utility payment history, and bank transaction analysis (weight 7%). The model is a gradient-boosted decision tree trained on historical loan performance data, retrained quarterly with the latest default data to maintain predictive accuracy.

Risk Grade Mapping

Score RangeGradeDescriptionExpected Default RateInterest Rate Range
750-900A+Excellent creditworthiness< 1%10-12%
700-749AVery good credit history1-2%12-14%
650-699B+Good credit profile2-4%14-16%
600-649BAverage creditworthiness4-7%16-18%
550-599C+Below average, higher risk7-12%18-22%
300-549CPoor credit, very high risk> 12%Not listed

Credit Assessment Service

public class CreditAssessmentService : ICreditAssessmentService
{
    private readonly ICreditBureauClient _bureauClient;
    private readonly IAlternativeDataClient _altDataClient;
    private readonly ICreditScoringModel _scoringModel;
    private readonly IAssessmentRepository _repository;

    public async Task<CreditAssessmentResult> AssessAsync(Guid borrowerId)
    {
        var bureauData = await _bureauClient.PullCreditReportAsync(borrowerId);
        var altData = await _altDataClient.GetAlternativeDataAsync(borrowerId);

        var features = new CreditFeatures
        {
            BureauScore = bureauData.CibilScore,
            DebtToIncomeRatio = CalculateDti(bureauData, altData),
            EmploymentStability = altData.MonthsAtCurrentJob,
            CreditHistoryLength = bureauData.CreditHistoryMonths,
            UtilizationRatio = bureauData.CreditUtilization,
            RecentInquiries = bureauData.RecentInquiries30Days,
            OnTimePaymentRate = bureauData.OnTimePaymentPercentage,
            BankBalanceStability = altData.BalanceStabilityIndex,
            IncomeGrowth = altData.IncomeGrowthRate,
            ExistingLoanCount = bureauData.ActiveLoans
        };

        var score = await _scoringModel.PredictAsync(features);
        var grade = MapToGrade(score);
        var rate = CalculateRate(grade, features);

        var result = new CreditAssessmentResult
        {
            BorrowerId = borrowerId,
            CreditScore = score,
            RiskGrade = grade,
            RecommendedRate = rate,
            MaxLoanAmount = CalculateMaxLoan(grade, features),
            DebtToIncomeRatio = features.DebtToIncomeRatio,
            AssessmentDate = DateTime.UtcNow
        };

        await _repository.SaveAssessmentAsync(result);
        return result;
    }

    private string MapToGrade(int score)
    {
        return score switch
        {
            >= 750 => "A+",
            >= 700 => "A",
            >= 650 => "B+",
            >= 600 => "B",
            >= 550 => "C+",
            _ => "C"
        };
    }

    private decimal CalculateRate(string grade, CreditFeatures features)
    {
        decimal baseRate = grade switch
        {
            "A+" => 10.0m, "A" => 12.0m, "B+" => 14.0m,
            "B" => 16.0m, "C+" => 18.0m, _ => 22.0m
        };
        if (features.DebtToIncomeRatio > 0.5m) baseRate += 2.0m;
        if (features.ExistingLoanCount > 5) baseRate += 1.0m;
        return Math.Min(baseRate, 24.0m);
    }
}
Model Governance: The credit scoring model must be audited quarterly. Track key metrics including Gini coefficient (target > 0.4), KS statistic (target > 0.3), and population stability index (target < 0.1). Retrain the model whenever the population stability index exceeds 0.25, indicating significant shift in the borrower population. Maintain a model registry with versioning so that predictions can be traced back to the exact model version used.

10. Loan Listing and Auction

Once a borrower passes credit assessment, the loan request is listed on the marketplace where investors can review and invest. The listing process must present all relevant information to investors while protecting borrower privacy. The platform supports both fixed-rate listings, where the platform sets the interest rate, and auction-based listings, where investors bid and the lowest competitive rate wins. The auction mechanism is particularly popular because it drives down borrowing costs through market competition while giving investors the opportunity to earn premium returns on higher-risk loans.

Loan Listing Data

FieldVisible to InvestorsPurpose
Loan AmountYesHow much the borrower needs
PurposeYesPersonal, business, education, medical
Risk GradeYesPlatform-assigned credit grade
TenureYesNumber of months for repayment
Max RateYesMaximum rate borrower is willing to pay
Monthly IncomeNoUsed in scoring, not shown to protect privacy
Employment TypeYesSalaried or self-employed
CityYesGeographic diversification for investors
Funded PercentageYesHow much has been funded so far

Auction Mechanism

In the auction model, borrowers set a maximum interest rate they are willing to pay, and investors bid with lower rates. The auction runs for a fixed period, typically 24 to 72 hours. At the end of the auction, the platform selects the lowest bids that together meet the loan amount. All winning bidders pay the same clearing rate, which is the highest accepted bid rate. This Dutch auction mechanism ensures fairness for both borrowers and investors.

public class AuctionService
{
    private readonly IAuctionRepository _auctionRepo;
    private readonly ILoanRepository _loanRepo;
    private readonly IMessageBus _messageBus;

    public async Task<AuctionResult> CloseAuctionAsync(Guid loanRequestId)
    {
        var auction = await _auctionRepo.GetActiveAuctionAsync(loanRequestId);
        if (auction == null || auction.EndDate > DateTime.UtcNow)
            throw new InvalidOperationException("Auction not ready to close");

        var bids = await _auctionRepo.GetBidsAsync(loanRequestId);
        var sortedBids = bids
            .OrderBy(b => b.OfferedRate)
            .ThenBy(b => b.SubmittedAt)
            .ToList();

        decimal totalFunded = 0m;
        decimal clearingRate = 0m;
        var winningBids = new List<AuctionBid>();

        foreach (var bid in sortedBids)
        {
            if (totalFunded >= auction.RequestedAmount) break;
            if (bid.OfferedRate > auction.BorrowerMaxRate) continue;

            decimal remainingAmount = auction.RequestedAmount - totalFunded;
            decimal allocatedAmount = Math.Min(bid.Amount, remainingAmount);

            winningBids.Add(new AuctionBid
            {
                BidId = bid.Id,
                InvestorId = bid.InvestorId,
                AllocatedAmount = allocatedAmount,
                FinalRate = bid.OfferedRate
            });
            totalFunded += allocatedAmount;
            clearingRate = bid.OfferedRate;
        }

        foreach (var wb in winningBids)
            wb.FinalRate = clearingRate;

        var result = new AuctionResult
        {
            LoanRequestId = loanRequestId,
            IsFullyFunded = totalFunded >= auction.RequestedAmount,
            TotalFunded = totalFunded,
            ClearingRate = clearingRate,
            WinningBids = winningBids
        };

        await _auctionRepo.SaveAuctionResultAsync(result);
        await _messageBus.PublishAsync(new AuctionClosedEvent { Result = result });
        return result;
    }
}

11. Investor Matching and Allocation

Investor matching determines how investor funds are allocated across loans to build diversified portfolios. The matching algorithm must consider the investor's risk appetite, desired returns, investment amount, portfolio diversification, and the available loan inventory. The goal is to maximize fund deployment while respecting investor preferences and maintaining portfolio health. The engine runs in two modes: real-time matching for manual investments and batch matching for auto-invest rules that execute daily during off-peak hours.

Allocation Algorithm

public class InvestorMatchingEngine
{
    private readonly ILoanRepository _loanRepo;
    private readonly IInvestorRepository _investorRepo;
    private readonly IAllocationRepository _allocationRepo;

    public async Task<List<AllocationResult>> MatchAndAllocateAsync()
    {
        var activeRules = await _investorRepo.GetActiveAutoInvestRulesAsync();
        var availableLoans = await _loanRepo.GetListedLoansAsync();
        var allocations = new List<AllocationResult>();

        var sortedInvestors = activeRules
            .OrderByDescending(r => r.Investor.VipStatus)
            .ThenByDescending(r => r.Investor.AvailableBalance)
            .ToList();

        foreach (var rule in sortedInvestors)
        {
            if (rule.Investor.AvailableBalance < rule.MinInvestmentAmount) continue;

            var matchingLoans = availableLoans
                .Where(l => rule.AllowedRiskGrades.Contains(l.RiskGrade))
                .Where(l => l.TenureMonths >= rule.MinTenureMonths)
                .Where(l => l.TenureMonths <= rule.MaxTenureMonths)
                .Where(l => l.InterestRate >= rule.MinExpectedRate)
                .ToList();

            decimal maxPerLoan = rule.Investor.AvailableBalance * rule.MaxConcentrationPercent;

            matchingLoans = matchingLoans
                .OrderBy(l => GetGradePriority(l.RiskGrade))
                .ThenByDescending(l => l.InterestRate)
                .ToList();

            foreach (var loan in matchingLoans)
            {
                if (rule.Investor.AvailableBalance < rule.MinInvestmentAmount) break;

                decimal investAmount = Math.Min(maxPerLoan, rule.Investor.AvailableBalance);
                investAmount = Math.Min(investAmount, loan.RemainingFunding);

                if (investAmount < rule.MinInvestmentAmount) continue;

                allocations.Add(new AllocationResult
                {
                    InvestorId = rule.InvestorId,
                    LoanId = loan.Id,
                    Amount = investAmount,
                    Rate = loan.InterestRate,
                    RiskGrade = loan.RiskGrade
                });

                rule.Investor.AvailableBalance -= investAmount;
            }
        }

        return allocations;
    }

    private int GetGradePriority(string grade)
    {
        return grade switch
        {
            "A+" => 0, "A" => 1, "B+" => 2, "B" => 3, "C+" => 4, _ => 5
        };
    }
}

Diversification Rules

RuleLimitRationale
Max per loan25% of portfolioSingle loan default impact limit
Max per borrower25% of portfolioPrevent concentration in one borrower
Max per risk grade40% of portfolioDiversify across risk levels
Min loans in portfolio20 loansMinimum diversification threshold
Max per city20% of portfolioGeographic risk mitigation
Max per purpose30% of portfolioSector risk mitigation

12. Escrow and Fund Management

Escrow management is the most critical component of a P2P lending platform from a trust and regulatory perspective. The RBI mandates that all investor funds must be held in a separate escrow account maintained with a scheduled commercial bank, and the platform cannot commingle these funds with its own operating capital. The escrow system must maintain accurate records of every investor's balance, process deposits and withdrawals with proper verification, and generate reconciliation reports that match the bank statements exactly. Any discrepancy between the platform's records and the bank balance must be flagged as a critical incident and investigated within 24 hours.

Escrow Account Structure

graph TD subgraph "Escrow Bank Account" EA[Master Escrow Account] EA --> I1[Investor A Sub-Account] EA --> I2[Investor B Sub-Account] EA --> I3[Investor C Sub-Account] end subgraph "Fund Flow" DEP[Deposit via NEFT/UPI] --> EA EA --> DIS[Disbursement to Borrower] EA --> WDR[Withdrawal to Investor Bank] REP[Repayment from Borrower] --> EA EA --> PAY[Investor Payout] end

Escrow Service Implementation

public class EscrowService : IEscrowService
{
    private readonly IEscrowRepository _escrowRepo;
    private readonly IBankingGateway _bankingGateway;
    private readonly IAuditLogger _auditLogger;

    public async Task<EscrowTransaction> ProcessDepositAsync(
        Guid investorId, decimal amount,
        string bankReference, IdempotencyKey idempotencyKey)
    {
        var existing = await _escrowRepo.GetByIdempotencyKeyAsync(idempotencyKey);
        if (existing != null) return existing;

        var bankVerification = await _bankingGateway
            .VerifyCreditAsync(bankReference, amount, investorId);

        if (!bankVerification.IsVerified)
            throw new FraudException("Bank credit verification failed");

        var transaction = new EscrowTransaction
        {
            Id = Guid.NewGuid(),
            InvestorId = investorId,
            Amount = amount,
            Type = EscrowTransactionType.Deposit,
            Status = EscrowTransactionStatus.Completed,
            BankReference = bankReference,
            IdempotencyKey = idempotencyKey,
            CreatedAt = DateTime.UtcNow
        };

        await _escrowRepo.ProcessAsync(transaction);
        await _escrowRepo.CreditBalanceAsync(investorId, amount);

        await _auditLogger.LogAsync(new AuditEntry
        {
            Action = "ESCROW_DEPOSIT",
            EntityId = transaction.Id.ToString(),
            UserId = investorId,
            Metadata = new { Amount = amount, BankReference = bankReference }
        });

        return transaction;
    }

    public async Task<EscrowTransaction> ProcessDisbursementAsync(
        Guid loanId, decimal amount, string borrowerBankAccount)
    {
        var totalCollected = await _escrowRepo
            .GetTotalCollectedForLoanAsync(loanId);

        if (totalCollected < amount)
            throw new InsufficientFundsException();

        var investments = await _escrowRepo
            .GetInvestmentsForLoanAsync(loanId);

        foreach (var investment in investments)
        {
            await _escrowRepo.DebitBalanceAsync(
                investment.InvestorId, investment.Amount);
        }

        var bankResult = await _bankingGateway
            .TransferFundsAsync(amount, borrowerBankAccount, $"Loan {loanId}");

        if (!bankResult.IsSuccess)
            throw new DisbursementException("Bank transfer failed");

        var transaction = new EscrowTransaction
        {
            Id = Guid.NewGuid(),
            LoanId = loanId,
            Amount = amount,
            Type = EscrowTransactionType.Disbursement,
            Status = EscrowTransactionStatus.Completed,
            BankReference = bankResult.Reference,
            CreatedAt = DateTime.UtcNow
        };

        await _escrowRepo.ProcessAsync(transaction);
        return transaction;
    }

    public async Task<ReconciliationResult> ReconcileAsync(DateTime date)
    {
        var dbBalance = await _escrowRepo.GetTotalBalanceAsync();
        var bankBalance = await _bankingGateway.GetEscrowBalanceAsync(date);
        var pendingTxns = await _escrowRepo.GetPendingTransactionsAsync(date);

        var result = new ReconciliationResult
        {
            Date = date,
            DatabaseBalance = dbBalance,
            BankBalance = bankBalance,
            Difference = dbBalance - bankBalance,
            PendingCount = pendingTxns.Count,
            IsReconciled = Math.Abs(dbBalance - bankBalance) < 0.01m
        };

        if (!result.IsReconciled)
        {
            await _auditLogger.AlertAsync(
                $"Reconciliation mismatch: DB={dbBalance}, Bank={bankBalance}",
                AlertSeverity.Critical);
        }

        return result;
    }
}
Regulatory Requirement: The RBI mandates that the P2P platform must maintain an escrow account with a scheduled commercial bank. All fund movements must go through this escrow. The platform must undergo quarterly reconciliation and submit reconciliation statements to the RBI as part of its compliance reporting. The escrow account must be audited by a chartered accountant every quarter, and the audit report must be submitted to the RBI within 30 days of the quarter end.

13. Repayment Collection

Repayment collection is the process of receiving monthly EMI payments from borrowers and distributing them to investors. The platform supports multiple collection methods including NACH (National Automated Clearing House) auto-debit, UPI mandate, net banking, and manual payment. NACH is the primary method for recurring collections as it provides a structured, bank-backed mandate system with automatic retries on failure. The collection process must handle partial payments, handle failed debits with scheduled retries, and update the loan schedule accurately in all scenarios.

Collection Pipeline

graph LR A[EMI Due Date] --> B[NACH File Generation] B --> C[Bank Submission] C --> D{Debit Success?} D -->|Yes| E[Credit Escrow] D -->|No| F[Retry on Day 3] F --> G{Retry Success?} G -->|Yes| E G -->|No| H[Retry on Day 7] H --> I{Final Retry?} I -->|Yes| E I -->|No| J[Mark Overdue] J --> K[Start Dunning] E --> L[Distribute to Investors]

Repayment Service

public class RepaymentCollectionService
{
    private readonly ILoanRepository _loanRepo;
    private readonly IEmiScheduleRepository _emiRepo;
    private readonly INachGateway _nachGateway;
    private readonly IInvestorPayoutService _payoutService;
    private readonly IOverdueService _overdueService;

    public async Task<BatchCollectionResult> ProcessDailyCollectionAsync(
        DateTime collectionDate)
    {
        var dueEmis = await _emiRepo.GetDueEmisAsync(collectionDate);
        var result = new BatchCollectionResult { Date = collectionDate };

        var nachFile = await _nachGateway.GenerateNachFileAsync(dueEmis);
        var submissionResult = await _nachGateway.SubmitFileAsync(nachFile);

        foreach (var emi in dueEmis)
        {
            var debitResult = await _nachGateway
                .CheckDebitStatusAsync(emi.LoanId, emi.EmiNumber);

            if (debitResult.Status == NachDebitStatus.Success)
            {
                await ProcessSuccessfulCollectionAsync(emi, debitResult);
                result.SuccessCount++;
                result.TotalCollected += emi.TotalAmount;
            }
            else if (debitResult.Status == NachDebitStatus.InsufficientFunds)
            {
                await ScheduleRetryAsync(emi);
                result.RetryCount++;
            }
            else
            {
                await HandleCollectionFailureAsync(emi, debitResult);
                result.FailureCount++;
            }
        }

        return result;
    }

    private async Task ProcessSuccessfulCollectionAsync(
        EmiRecord emi, NachDebitResult debitResult)
    {
        emi.Status = EmiStatus.Paid;
        emi.PaidDate = DateTime.UtcNow;
        emi.PaidAmount = emi.TotalAmount;
        emi.BankReference = debitResult.Reference;
        await _emiRepo.UpdateAsync(emi);

        var loan = await _loanRepo.GetByIdAsync(emi.LoanId);
        loan.OutstandingPrincipal -= emi.PrincipalPart;
        loan.PaidEmiCount++;
        loan.DaysPastDue = 0;

        if (loan.PaidEmiCount >= loan.TenureMonths)
            loan.Status = LoanStatus.Closed;

        await _loanRepo.UpdateAsync(loan);
        await _payoutService.DistributeEmiAsync(emi.LoanId, emi);
    }

    private async Task HandleCollectionFailureAsync(
        EmiRecord emi, NachDebitResult debitResult)
    {
        emi.Status = EmiStatus.Overdue;
        emi.OverdueFrom = emi.DueDate.AddDays(1);
        await _emiRepo.UpdateAsync(emi);

        var loan = await _loanRepo.GetByIdAsync(emi.LoanId);
        loan.DaysPastDue = (DateTime.UtcNow - emi.DueDate).Days;
        await _loanRepo.UpdateAsync(loan);

        await _overdueService.InitiateDunningAsync(emi.LoanId, emi.EmiNumber);
    }
}

Investor Payout Distribution

public class InvestorPayoutService
{
    public async Task DistributeEmiAsync(Guid loanId, EmiRecord emi)
    {
        var investments = await _investmentRepo.GetActiveInvestmentsAsync(loanId);
        decimal totalInvested = investments.Sum(i => i.Amount);

        foreach (var investment in investments)
        {
            decimal sharePercent = investment.Amount / totalInvested;
            decimal principalPayout = Math.Round(emi.PrincipalPart * sharePercent, 2);
            decimal interestPayout = Math.Round(emi.InterestPart * sharePercent, 2);
            decimal platformFee = Math.Round(interestPayout * 0.01m, 2);
            decimal netPayout = principalPayout + interestPayout - platformFee;

            var payout = new InvestmentPayout
            {
                Id = Guid.NewGuid(),
                InvestmentId = investment.Id,
                LoanId = loanId,
                EmiNumber = emi.EmiNumber,
                PrincipalComponent = principalPayout,
                InterestComponent = interestPayout,
                PlatformFee = platformFee,
                NetAmount = netPayout,
                Status = PayoutStatus.Processing,
                CreatedAt = DateTime.UtcNow
            };

            await _payoutRepo.SaveAsync(payout);
            await _escrowService.CreditBalanceAsync(
                investment.InvestorId, netPayout);

            investment.ReturnsReceived += netPayout;
            await _investmentRepo.UpdateAsync(investment);
        }
    }
}

14. Default and Recovery

Default management is a critical process that directly impacts investor returns and platform reputation. A loan is classified as overdue when the EMI is not paid by the due date. The RBI defines stages of delinquency that trigger different recovery actions. The platform must implement an automated dunning process that escalates through increasingly aggressive recovery measures, from gentle reminders to legal notices to engagement with recovery agents. The dunning system must be careful not to harass borrowers while still maintaining pressure to recover funds, as excessive collection tactics can result in regulatory penalties and reputational damage.

Delinquency Stages

DPD RangeClassificationActionImpact
1-30 daysOverdueSMS and email reminders, auto-retry debitMinor - recoverable
31-60 daysSubstandardPhone calls, field agent visitNoticeable - early intervention needed
61-90 daysDoubtfulFormal legal notice, recovery agencySignificant - recovery uncertain
91-180 daysLoss CategoryDebt collection agency, legal proceedingsSevere - partial recovery expected
180+ daysWritten OffFinal recovery attempt, tax write-offCritical - investor loss

Recovery Service

public class DefaultRecoveryService
{
    private readonly ILoanRepository _loanRepo;
    private readonly IRecoveryAgentClient _recoveryClient;
    private readonly IDunningService _dunningService;
    private readonly IInvestorNotificationService _notifService;

    public async Task<DailyDefaultResult> ProcessDailyDefaultsAsync()
    {
        var overdueLoans = await _loanRepo.GetOverdueLoansAsync();
        var result = new DailyDefaultResult();

        foreach (var loan in overdueLoans)
        {
            int dpd = (DateTime.UtcNow - loan.LastPaymentDate).Days;
            loan.DaysPastDue = dpd;

            switch (dpd)
            {
                case int n when n >= 1 && n <= 30:
                    await _dunningService.SendRemindersAsync(loan);
                    result.StageOneCount++;
                    break;
                case int n when n >= 31 && n <= 60:
                    await _dunningService.InitiatePhoneCallAsync(loan);
                    await _dunningService.ScheduleFieldVisitAsync(loan);
                    result.StageTwoCount++;
                    break;
                case int n when n >= 61 && n <= 90:
                    await _dunningService.SendLegalNoticeAsync(loan);
                    await _recoveryClient.AssignAgentAsync(
                        loan.Id, RecoveryPriority.High);
                    result.StageThreeCount++;
                    break;
                case int n when n >= 91 && n < 180:
                    await _recoveryClient.InitiateLegalProceedingAsync(loan.Id);
                    result.StageFourCount++;
                    break;
                case int n when n >= 180:
                    await ProcessChargeOffAsync(loan);
                    result.ChargeOffCount++;
                    break;
            }

            await _loanRepo.UpdateAsync(loan);
        }

        await _notifService.NotifyInvestorsOfDefaultsAsync(overdueLoans);
        return result;
    }

    private async Task ProcessChargeOffAsync(Loan loan)
    {
        loan.Status = LoanStatus.WrittenOff;
        loan.WrittenOffDate = DateTime.UtcNow;
        loan.WriteOffAmount = loan.OutstandingPrincipal;

        var investments = await _investmentRepo
            .GetActiveInvestmentsAsync(loan.Id);
        foreach (var investment in investments)
        {
            decimal loss = investment.Amount *
                (loan.OutstandingPrincipal / loan.PrincipalAmount);
            investment.Status = InvestmentStatus.WrittenOff;
            investment.LossAmount = loss;
            await _investmentRepo.UpdateAsync(investment);
        }

        await _recoveryClient.InitiateFinalRecoveryAsync(loan.Id);
    }
}
Recovery Best Practices: Maintain a blacklist of recovery practices that are prohibited under RBI guidelines, including contacting borrowers before 8 AM or after 9 PM, using threatening language, contacting borrowers' employers without consent, or sharing borrower information with third parties. All recovery calls must be recorded and stored for at least 3 years for audit purposes. Implement a borrower hardship program that allows restructuring of loans for borrowers who have experienced genuine financial difficulties, subject to investor approval.

15. Auto-Invest Rules Engine

The auto-invest feature allows investors to set rules that automatically invest their idle funds in matching loans. This increases fund utilization and improves investor returns. The rules engine evaluates each new loan listing against all active investor rules and triggers investments for matches. The engine must handle concurrent execution, prevent overspending, and respect diversification limits. The auto-invest engine is one of the most latency-sensitive components because it must evaluate hundreds of investor rules within seconds of a new loan being listed.

Auto-Invest Rule Configuration

public class AutoInvestRule
{
    public Guid Id { get; set; }
    public Guid InvestorId { get; set; }
    public bool IsActive { get; set; }

    public decimal MinInvestmentAmount { get; set; }
    public decimal MaxInvestmentPerLoan { get; set; }
    public decimal MaxTotalInvestment { get; set; }

    public string[] AllowedRiskGrades { get; set; }
    public decimal MinExpectedRate { get; set; }
    public int MinTenureMonths { get; set; }
    public int MaxTenureMonths { get; set; }
    public string[] AllowedPurposes { get; set; }

    public decimal MaxConcentrationPercent { get; set; }
    public int MinPortfolioSize { get; set; }
    public string[] ExcludedBorrowerIds { get; set; }

    public AutoInvestFrequency Frequency { get; set; }
    public TimeSpan? PreferredExecutionTime { get; set; }
}

Rule Evaluation Engine

public class AutoInvestEngine
{
    private readonly IRuleRepository _ruleRepo;
    private readonly ILoanRepository _loanRepo;
    private readonly IInvestmentService _investmentService;
    private readonly IEscrowService _escrowService;
    private readonly IDistributedLock _lockManager;

    public async Task ProcessLoanListingAsync(Guid loanRequestId)
    {
        var loan = await _loanRepo.GetLoanRequestAsync(loanRequestId);
        var matchingRules = await _ruleRepo.GetMatchingRulesAsync(loan);

        foreach (var rule in matchingRules.OrderBy(r => r.Investor.Priority))
        {
            var lockKey = $"auto-invest:{rule.InvestorId}";

            using (await _lockManager.AcquireAsync(lockKey, TimeSpan.FromSeconds(30)))
            {
                var balance = await _escrowService
                    .GetAvailableBalanceAsync(rule.InvestorId);

                if (balance < rule.MinInvestmentAmount) continue;

                var currentExposure = await _investmentService
                    .GetCurrentExposureAsync(rule.InvestorId, loan.BorrowerId);

                var maxExposure = rule.Investor.TotalInvested *
                    rule.MaxConcentrationPercent / 100;

                if (currentExposure >= maxExposure) continue;

                decimal investAmount = Math.Min(
                    rule.MaxInvestmentPerLoan, balance);
                investAmount = Math.Min(
                    investAmount, maxExposure - currentExposure);
                investAmount = Math.Min(
                    investAmount, loan.RemainingFunding);

                if (investAmount < rule.MinInvestmentAmount) continue;

                await _investmentService.PlaceAutoInvestmentAsync(
                    rule.InvestorId, loanRequestId, investAmount);
            }
        }
    }
}

Auto-Invest Frequency Options

FrequencyDescriptionBest For
RealTimeInvest immediately when loan matches rulesInvestors with idle funds wanting max deployment
DailyRun matching once daily during off-peak hoursConservative investors who want to review allocations
WeeklyRun matching once per weekPassive investors who check portfolio infrequently

16. Secondary Market and Loan Trading

The secondary market allows investors to sell their loan parts to other investors before the loan matures. This provides liquidity to investors who need early exit and creates trading opportunities for investors seeking specific risk-return profiles. The secondary market must handle fair pricing based on remaining cash flows and current market conditions, ownership transfer that updates all downstream payment distributions, and regulatory compliance since the RBI has specific guidelines on secondary market transactions for P2P loans. The secondary market is essential for investor retention because without an exit option, many investors would not commit funds to longer-tenure loans.

Secondary Market Design

public class SecondaryMarketService
{
    private readonly ILoanPartRepository _loanPartRepo;
    private readonly IPriceDiscoveryEngine _priceEngine;
    private readonly ITransferService _transferService;
    private readonly IInvestorPayoutService _payoutService;

    public async Task<ListingResult> CreateListingAsync(
        Guid sellerId, Guid investmentId, decimal askingPrice)
    {
        var investment = await _loanPartRepo
            .GetInvestmentAsync(investmentId);

        if (investment.InvestorId != sellerId)
            throw new UnauthorizedException("Not the owner");

        if (investment.Status != InvestmentStatus.Active)
            throw new InvalidOperationException("Loan part not active");

        var fairValue = await _priceEngine
            .CalculateFairValueAsync(investment);

        if (askingPrice < fairValue * 0.9m)
            throw new ValidationException("Asking price too low");

        var listing = new SecondaryMarketListing
        {
            Id = Guid.NewGuid(),
            SellerId = sellerId,
            InvestmentId = investmentId,
            LoanId = investment.LoanId,
            Units = investment.Amount,
            AskingPrice = askingPrice,
            FairValue = fairValue,
            OutstandingPrincipal = investment.OutstandingPrincipal,
            MonthlyEmi = investment.MonthlyEmi,
            RemainingTenure = investment.RemainingTenure,
            RiskGrade = investment.RiskGrade,
            Status = ListingStatus.Active,
            ListedAt = DateTime.UtcNow,
            ExpiresAt = DateTime.UtcNow.AddHours(48)
        };

        await _loanPartRepo.SaveListingAsync(listing);
        return new ListingResult
        {
            ListingId = listing.Id,
            FairValue = fairValue
        };
    }

    public async Task<TradeResult> ExecuteTradeAsync(
        Guid listingId, Guid buyerId)
    {
        var listing = await _loanPartRepo.GetListingAsync(listingId);

        if (listing.Status != ListingStatus.Active)
            throw new InvalidOperationException("Listing not active");

        if (listing.ExpiresAt < DateTime.UtcNow)
            throw new InvalidOperationException("Listing expired");

        var buyerBalance = await _escrowService
            .GetAvailableBalanceAsync(buyerId);

        if (buyerBalance < listing.AskingPrice)
            throw new InsufficientFundsException();

        await _transferService.ExecuteTradeAsync(
            buyerId, listing.SellerId, listing.AskingPrice);

        var buyerInvestment = new Investment
        {
            Id = Guid.NewGuid(),
            InvestorId = buyerId,
            LoanId = listing.LoanId,
            Amount = listing.Units,
            Status = InvestmentStatus.Active,
            AcquiredVia = "SecondaryMarket",
            OriginalInvestmentId = listing.InvestmentId,
            CreatedAt = DateTime.UtcNow
        };

        await _loanPartRepo.SaveInvestmentAsync(buyerInvestment);

        var sellerInvestment = await _loanPartRepo
            .GetInvestmentAsync(listing.InvestmentId);
        sellerInvestment.Status = InvestmentStatus.Sold;
        sellerInvestment.SoldAt = DateTime.UtcNow;
        await _loanPartRepo.UpdateInvestmentAsync(sellerInvestment);

        listing.Status = ListingStatus.Sold;
        listing.BuyerId = buyerId;
        listing.SoldAt = DateTime.UtcNow;
        await _loanPartRepo.UpdateListingAsync(listing);

        return new TradeResult
        {
            TradeId = Guid.NewGuid(),
            ListingId = listingId,
            BuyerId = buyerId,
            SellerId = listing.SellerId,
            Amount = listing.AskingPrice,
            ExecutedAt = DateTime.UtcNow
        };
    }
}
Price Discovery: The fair value of a loan part is calculated as the present value of all remaining cash flows (principal + interest) discounted at the current market rate for that risk grade. When market rates rise, existing loan prices fall, and vice versa. The platform must cap the maximum discount at 30% below face value to prevent fire sales that could destabilize investor confidence. Listings expire after 48 hours to prevent stale pricing.

17. Investor Dashboard

The investor dashboard provides a comprehensive view of the investor's portfolio, including current holdings, returns, default status, cash flow projections, and available balance. The dashboard must load quickly with real-time data and support drill-down into individual loans. Performance is critical because investors check their dashboards frequently, especially during market volatility or when repayment cycles are running. The dashboard aggregates data from multiple services and presents it in a unified, actionable format.

Dashboard Data Model

public class InvestorDashboard
{
    public decimal TotalInvested { get; set; }
    public decimal CurrentValue { get; set; }
    public decimal TotalReturns { get; set; }
    public decimal TotalDefaults { get; set; }
    public decimal NetReturn { get; set; }
    public decimal AnnualizedYield { get; set; }
    public decimal Xirr { get; set; }

    public PortfolioBreakdown ByRiskGrade { get; set; }
    public PortfolioBreakdown ByPurpose { get; set; }
    public PortfolioBreakdown ByTenure { get; set; }

    public int ActiveLoans { get; set; }
    public int OverdueLoans { get; set; }
    public int DefaultedLoans { get; set; }
    public decimal DefaultRate { get; set; }
    public decimal WeightedAverageRate { get; set; }
    public decimal WeightedAverageTenure { get; set; }

    public List<MonthlyCashFlow> FutureCashFlows { get; set; }
    public List<RecentTransaction> RecentActivity { get; set; }

    public decimal AvailableBalance { get; set; }
    public decimal PendingPayouts { get; set; }
    public decimal InvestedInEscrow { get; set; }
}

public class MonthlyCashFlow
{
    public int Month { get; set; }
    public decimal PrincipalReturn { get; set; }
    public decimal InterestIncome { get; set; }
    public decimal TotalInflow { get; set; }
    public int ActiveLoanCount { get; set; }
}

public class DashboardService
{
    private readonly IDashboardRepository _repo;
    private readonly IDistributedCache _cache;

    public async Task<InvestorDashboard> GetDashboardAsync(Guid investorId)
    {
        var cacheKey = $"dashboard:{investorId}";
        var cached = await _cache.GetAsync<InvestorDashboard>(cacheKey);
        if (cached != null) return cached;

        var dashboard = await _repo.BuildDashboardAsync(investorId);
        await _cache.SetAsync(cacheKey, dashboard,
            TimeSpan.FromMinutes(5));

        return dashboard;
    }
}

Dashboard Performance Metrics

MetricTargetStrategy
Dashboard load time< 2 secondsRedis cache, read replicas, CDN for static assets
Cash flow projection< 500msPre-computed daily, cached in Redis
XIRR calculation< 3 secondsBackground job, result cached for 1 hour
Portfolio breakdown< 1 secondMaterialized views updated every 15 minutes
Recent activity< 200msLast 50 transactions cached per user

18. Regulatory Compliance — RBI NBFC-P2P

The Reserve Bank of India (RBI) introduced comprehensive guidelines for NBFC-P2P lending platforms in 2017, subsequently amended in 2019 and 2022. These regulations establish the rules for platform operation, investor limits, borrowing limits, fund flow requirements, and reporting obligations. Non-compliance can result in penalties, license revocation, or criminal prosecution. Every engineering decision in the platform must be made with regulatory compliance in mind. The regulatory landscape for P2P lending in India is complex and evolving, requiring close coordination between the legal, compliance, and engineering teams.

Key RBI Regulations

RegulationRequirementImplementation
Investor LimitMax 10 lakh per borrower across all platformsReal-time aggregation check via bureau data
Borrower LimitMax 50 lakh total borrowing across all platformsBureau pull plus self-declaration
Interest Rate CapMax 24% per annum all-inclusiveSystem-enforced hard cap in pricing engine
Loan TenureMax 36 monthsHard cap in loan application validation
Escrow RequirementSeparate escrow with scheduled commercial bankBank partnership, daily reconciliation
No Principal GuaranteePlatform cannot guarantee returns to investorsClear risk disclosure, no guaranteed return products
Information UtilityRegister with RBI Information UtilityAPI integration for loan registration
ReportingQuarterly returns to RBIAutomated report generation and submission
Data LocalizationAll data must reside in IndiaIndia-only cloud regions, no cross-border data transfer
Capital AdequacyMinimum net owned fund of 2 croreMonthly capital adequacy monitoring dashboard

Compliance Service Implementation

public class RegulatoryComplianceService
{
    private readonly IBorrowerRepository _borrowerRepo;
    private readonly IInvestorRepository _investorRepo;
    private readonly IBureauClient _bureauClient;
    private readonly IRbiReportingClient _rbiClient;

    public async Task<ComplianceCheckResult> ValidateBorrowingLimitAsync(
        Guid borrowerId, decimal requestedAmount)
    {
        var borrower = await _borrowerRepo.GetByIdAsync(borrowerId);

        // Check total outstanding across all P2P platforms via bureau
        var bureauReport = await _bureauClient
            .GetPeerToPeerExposureAsync(borrower.PanNumber);

        decimal existingExposure = bureauReport.P2PLoanOutstanding;
        decimal proposedTotal = existingExposure + requestedAmount;

        var result = new ComplianceCheckResult();

        // RBI limit: max 50 lakh total P2P borrowing
        if (proposedTotal > 5_00_000m)
        {
            result.IsCompliant = false;
            result.Violation = "Exceeds RBI borrowing limit of 10 lakh";
            result.MaxAllowed = 5_00_000m - existingExposure;
            return result;
        }

        // Check interest rate cap
        var assessment = await _borrowerRepo
            .GetLatestAssessmentAsync(borrowerId);
        if (assessment.RecommendedRate > 24.0m)
        {
            result.IsCompliant = false;
            result.Violation = "Rate exceeds RBI cap of 24%";
            return result;
        }

        result.IsCompliant = true;
        return result;
    }

    public async Task<ComplianceCheckResult> ValidateInvestorLimitAsync(
        Guid investorId, Guid borrowerId, decimal investmentAmount)
    {
        // Check per-borrower limit across all platforms
        var existingExposure = await _investorRepo
            .GetExposureToBorrowerAsync(investorId, borrowerId);

        decimal proposedTotal = existingExposure + investmentAmount;

        // RBI limit: max 10 lakh per borrower
        if (proposedTotal > 10_00_000m)
        {
            return new ComplianceCheckResult
            {
                IsCompliant = false,
                Violation = "Exceeds RBI investor limit of 10 lakh per borrower",
                MaxAllowed = 10_00_000m - existingExposure
            };
        }

        return new ComplianceCheckResult { IsCompliant = true };
    }

    public async Task GenerateQuarterlyReportAsync(DateTime quarterEnd)
    {
        var report = new RbiQuarterlyReport
        {
            ReportingPeriod = quarterEnd,
            TotalLoansOriginated = await GetOriginatedCountAsync(quarterEnd),
            TotalAmountDisbursed = await GetDisbursedAmountAsync(quarterEnd),
            AverageLoanSize = await GetAverageLoanSizeAsync(quarterEnd),
            AverageInterestRate = await GetAverageRateAsync(quarterEnd),
            NPAPercentage = await GetNpaPercentageAsync(quarterEnd),
            TotalInvestors = await GetActiveInvestorCountAsync(),
            TotalBorrowers = await GetActiveBorrowerCountAsync(),
            EscrowBalance = await GetEscrowBalanceAsync(quarterEnd),
            CapitalAdequacyRatio = await GetCarAsync(),
            ComplaintCount = await GetComplaintCountAsync(quarterEnd)
        };

        await _rbiClient.SubmitReportAsync(report);
    }
}

Reporting Schedule

ReportFrequencyDeadlineRecipient
Quarterly ReturnsQuarterly15 days after quarter endRBI Regional Office
Annual ReturnsAnnuallyMarch 31RBI
Audit ReportAnnuallyJune 30RBI
Escrow ReconciliationQuarterly30 days after quarter endAuditor + RBI
Capital AdequacyMonthly10th of following monthInternal
Fraud ReportAs neededWithin 24 hoursRBI + Law Enforcement

19. Fraud Prevention and Detection

Fraud prevention is critical in P2P lending because the platform facilitates direct financial transactions between individuals. Common fraud vectors include identity theft (using stolen KYC documents to create fake borrower accounts), income fraud (inflating income to qualify for larger loans), collusion fraud (a single person creating multiple borrower and investor accounts to manipulate the system), and money laundering (using the platform to move funds between controlled accounts). The fraud detection system must operate in real-time for transaction monitoring and in batch mode for pattern detection across the entire user base.

Fraud Detection Rules

RuleDetection MethodAction
Duplicate PANExact match on PAN across accountsBlock registration, alert compliance
Duplicate AadhaarHash match on Aadhaar numberBlock registration, alert compliance
Velocity checkMultiple loan applications in short periodFlag for manual review
Income anomalyReported income vs bank statementRequest additional proof, flag if discrepancy > 30%
Device fingerprintMultiple accounts from same deviceAlert, investigate for collusion
GeolocationIP address mismatch with registered addressAdditional verification step
Collusion patternGraph analysis of borrower-investor relationshipsFreeze accounts, compliance investigation
Behavioral biometricsTyping patterns, navigation behaviorFlag anomalies for review
Bank statement manipulationPDF metadata analysis, digital signature checkReject document, request direct bank API access

Fraud Detection Service

public class FraudDetectionService
{
    private readonly IFraudRuleEngine _ruleEngine;
    private readonly IDeviceFingerprintService _deviceService;
    private readonly IGraphAnalysisService _graphService;
    private readonly IFraudRepository _fraudRepo;

    public async Task<FraudAssessmentResult> AssessRegistrationAsync(
        RegistrationRequest request)
    {
        var result = new FraudAssessmentResult();

        // Rule 1: Duplicate identity check
        var existingUser = await _fraudRepo
            .FindByPanAsync(request.PanNumber);
        if (existingUser != null)
        {
            result.RiskScore += 90;
            result.Flags.Add("DUPLICATE_PAN");
        }

        // Rule 2: Device fingerprint check
        var deviceUsers = await _deviceService
            .FindUsersByDeviceAsync(request.DeviceFingerprint);
        if (deviceUsers.Count > 3)
        {
            result.RiskScore += 70;
            result.Flags.Add("MULTIPLE_ACCOUNTS_SAME_DEVICE");
        }

        // Rule 3: IP reputation check
        var ipInfo = await _deviceService.GetIpInfoAsync(request.IpAddress);
        if (ipInfo.IsVpn || ipInfo.IsProxy)
        {
            result.RiskScore += 40;
            result.Flags.Add("VPN_OR_PROXY_DETECTED");
        }

        // Rule 4: Email domain reputation
        if (IsDisposableEmail(request.Email))
        {
            result.RiskScore += 50;
            result.Flags.Add("DISPOSABLE_EMAIL");
        }

        // Determine action
        result.Action = result.RiskScore switch
        {
            >= 80 => FraudAction.Block,
            >= 50 => FraudAction.ManualReview,
            >= 30 => FraudAction.AdditionalVerification,
            _ => FraudAction.Approve
        };

        await _fraudRepo.SaveAssessmentAsync(request.UserId, result);
        return result;
    }

    public async Task DetectCollusionPatternsAsync()
    {
        var graph = await _graphService.BuildLendingGraphAsync();

        // Find strongly connected components
        var sccs = graph.FindStronglyConnectedComponents();

        foreach (var component in sccs)
        {
            if (component.NodeCount > 3)
            {
                // Potential collusion ring
                await _fraudRepo.RaiseCollusionAlertAsync(
                    component.UserIds,
                    $"Suspected collusion ring with {component.NodeCount} users");

                // Freeze all accounts in the ring
                foreach (var userId in component.UserIds)
                {
                    await _fraudRepo.FreezeAccountAsync(userId,
                        "Suspected collusion - under investigation");
                }
            }
        }
    }
}
False Positive Management: The fraud detection system must maintain a false positive rate below 2%. Every blocked user must be notified with a clear explanation and provided an easy appeal process. The compliance team must review all blocks within 48 hours and either confirm the block or release the account with compensation for the inconvenience. Track false positive metrics monthly and tune rules accordingly.

20. Documentation and E-Sign

Every P2P loan must be accompanied by a legally binding loan agreement that specifies the loan amount, interest rate, tenure, repayment schedule, default consequences, and the rights and obligations of both borrower and investors. The RBI requires that these agreements be executed with digital signatures using Digital Signature Certificates (DSC) issued by licensed certifying authorities. The document management system must generate templated agreements, present them for e-signature, store the signed documents securely, and provide them for download by all parties.

Document Generation Pipeline

graph LR A[Loan Approved] --> B[Generate Agreement Template] B --> C[Fill Borrower Details] C --> D[Fill Loan Terms] D --> E[Generate PDF] E --> F[Send for E-Sign] F --> G{Signed?} G -->|Yes| H[Store in S3] G -->|No| I[Reminder after 24h] I --> F H --> J[Notify Parties]

Document Service Implementation

public class DocumentService
{
    private readonly ITemplateEngine _templateEngine;
    private readonly IPdfGenerator _pdfGenerator;
    private readonly IEsignProvider _esignProvider;
    private readonly IBlobStorage _blobStorage;
    private readonly IDocumentRepository _docRepo;

    public async Task<LoanAgreement> GenerateAndSignAsync(Guid loanId)
    {
        var loan = await _loanRepo.GetByIdAsync(loanId);
        var borrower = await _borrowerRepo.GetByIdAsync(loan.BorrowerId);
        var investors = await _investmentRepo.GetInvestorsAsync(loanId);

        // Generate agreement from template
        var templateData = new AgreementTemplateData
        {
            LoanId = loanId.ToString(),
            BorrowerName = borrower.FullName,
            BorrowerPan = borrower.PanNumber,
            LoanAmount = loan.PrincipalAmount,
            InterestRate = loan.AnnualInterestRate,
            TenureMonths = loan.TenureMonths,
            MonthlyEmi = loan.MonthlyEmi,
            DisbursementDate = loan.DisbursedAt.ToString("dd MMMM yyyy"),
            MaturityDate = loan.MaturityDate.ToString("dd MMMM yyyy"),
            InvestorNames = investors.Select(i => i.FullName).ToList(),
            PlatformName = "P2P Lending Platform",
            AgreementDate = DateTime.UtcNow.ToString("dd MMMM yyyy")
        };

        var htmlContent = await _templateEngine.RenderAsync(
            "LoanAgreement", templateData);
        var pdfBytes = await _pdfGenerator.GenerateAsync(htmlContent);

        // Store PDF in blob storage
        var blobPath = $"agreements/{loanId}/loan_agreement_v1.pdf";
        await _blobStorage.UploadAsync(blobPath, pdfBytes, "application/pdf");

        // Send for e-signature to borrower
        var esignResult = await _esignProvider.SendForSigningAsync(
            new EsignRequest
            {
                DocumentPath = blobPath,
                SignerEmail = borrower.Email,
                SignerName = borrower.FullName,
                ExpiryHours = 72
            });

        var agreement = new LoanAgreement
        {
            Id = Guid.NewGuid(),
            LoanId = loanId,
            Version = 1,
            FilePath = blobPath,
            EsignRequestId = esignResult.RequestId,
            Status = AgreementStatus.PendingSignature,
            CreatedAt = DateTime.UtcNow
        };

        await _docRepo.SaveAsync(agreement);
        return agreement;
    }

    public async Task OnEsignCompletedAsync(string esignRequestId)
    {
        var agreement = await _docRepo
            .GetByEsignRequestIdAsync(esignRequestId);

        agreement.Status = AgreementStatus.Signed;
        agreement.SignedAt = DateTime.UtcNow;
        await _docRepo.UpdateAsync(agreement);

        // Notify all parties
        var loan = await _loanRepo.GetByIdAsync(agreement.LoanId);
        await _notifService.NotifyAsync(loan.BorrowerId,
            "Your loan agreement has been signed successfully.");
    }
}

E-Signature Provider Comparison

ProviderComplianceCost per SignIntegration
LeegalityITA 2000 compliant5 INRREST API + SDK
SignDeskITA 2000 compliant8 INRREST API
EmudhraCertifying Authority licensed15 INRDSC + API
NeSLRBI approved10 INRAPI

21. Notifications and Communication

The notification system must deliver timely, relevant messages to borrowers and investors across multiple channels. Notifications are triggered by platform events such as KYC approval, loan listing, investment confirmation, EMI due reminders, payment confirmations, and default alerts. Each notification must be delivered through the preferred channel (SMS, email, push notification) with fallback mechanisms if the primary channel fails. The system must respect user preferences for notification frequency and comply with TRAI regulations for commercial SMS messages.

Notification Templates

EventChannelTemplatePriority
KYC ApprovedEmail + PushWelcome message with next stepsHigh
Loan ListedPushNew loan available notificationMedium
Investment ConfirmedSMS + EmailInvestment receipt with detailsHigh
EMI Due ReminderSMS + Push3-day and 1-day before EMIHigh
Payment ReceivedSMS + EmailPayment confirmation and receiptHigh
Default AlertEmailLoan overdue notificationCritical
Monthly StatementEmailPortfolio performance reportLow
Regulatory UpdateEmailPolicy changes or compliance noticesMedium

Notification Service

public class NotificationService
{
    private readonly ISmsGateway _smsGateway;
    private readonly IEmailGateway _emailGateway;
    private readonly IPushNotificationService _pushService;
    private readonly INotificationRepository _notifRepo;
    private readonly IUserPreferenceRepository _prefRepo;

    public async Task SendAsync(NotificationEvent notification)
    {
        var user = await _prefRepo.GetUserAsync(notification.UserId);
        var preferences = await _prefRepo
            .GetPreferencesAsync(notification.UserId);

        if (!preferences.IsChannelEnabled(notification.Channel))
            return;

        var template = await GetTemplateAsync(
            notification.EventType, notification.Channel);
        var content = template.Render(notification.Data);

        NotificationRecord record = notification.Channel switch
        {
            Channel.Sms => await SendSmsAsync(user.Phone, content),
            Channel.Email => await SendEmailAsync(
                user.Email, template.Subject, content),
            Channel.Push => await SendPushAsync(
                user.DeviceTokens, template.Title, content),
            _ => throw new ArgumentException("Unknown channel")
        };

        await _notifRepo.SaveAsync(record);
    }

    private async Task<NotificationRecord> SendSmsAsync(
        string phone, string content)
    {
        var result = await _smsGateway.SendAsync(phone, content);
        return new NotificationRecord
        {
            Id = Guid.NewGuid(),
            Channel = Channel.Sms,
            Recipient = phone,
            Content = content,
            Status = result.IsSuccess
                ? NotificationStatus.Delivered
                : NotificationStatus.Failed,
            SentAt = DateTime.UtcNow,
            ProviderMessageId = result.MessageId
        };
    }
}

22. Analytics and Reporting

The analytics platform provides insights into platform performance, investor returns, borrower behavior, and operational metrics. The analytics system processes data from multiple sources, aggregates it into meaningful metrics, and presents it through dashboards and automated reports. Key stakeholders include the platform management team (for business metrics), the compliance team (for regulatory reporting), the risk team (for portfolio health), and investor relations (for investor communication). The analytics pipeline must handle both real-time dashboards and batch reporting with appropriate technology choices for each use case.

Key Platform Metrics

MetricFormulaTargetAlert Threshold
Default RateDefaults / Total Originations< 3%> 5%
Average YieldWeighted average investor return12-16%< 10%
Recovery RateAmount recovered / Amount defaulted> 40%< 25%
Fund UtilizationFunded loans / Listed loans> 80%< 60%
Avg Time to FundAverage hours from listing to fully funded< 48 hours> 96 hours
Investor RetentionActive investors / Total registered> 60%< 40%
Borrower NPSNet Promoter Score> 50< 30
Collection EfficiencyAmount collected / Amount due> 97%< 93%

Analytics Pipeline Architecture

public class AnalyticsPipeline
{
    private readonly IKafkaConsumer _kafkaConsumer;
    private readonly IAnalyticsRepository _analyticsRepo;
    private readonly IDashboardCache _dashboardCache;

    public async Task ProcessEventAsync(PlatformEvent evt)
    {
        switch (evt)
        {
            case LoanDisbursedEvent e:
                await ProcessLoanDisbursed(e);
                break;
            case EmiReceivedEvent e:
                await ProcessEmiReceived(e);
                break;
            case LoanDefaultedEvent e:
                await ProcessLoanDefaulted(e);
                break;
            case InvestmentPlacedEvent e:
                await ProcessInvestmentPlaced(e);
                break;
        }

        // Update real-time counters
        await _analyticsRepo.IncrementCounterAsync(
            evt.EventType, evt.Timestamp.Date);

        // Invalidate dashboard caches
        await _dashboardCache.InvalidateRelatedCachesAsync(evt);
    }

    private async Task ProcessLoanDisbursed(LoanDisbursedEvent e)
    {
        await _analyticsRepo.RecordMetricAsync("originations",
            e.Amount, e.Timestamp);
        await _analyticsRepo.UpdatePortfolioMetricAsync(
            e.RiskGrade, e.Amount, "disbursed");
    }

    private async Task ProcessEmiReceived(EmiReceivedEvent e)
    {
        await _analyticsRepo.RecordMetricAsync("collections",
            e.Amount, e.Timestamp);
        await _analyticsRepo.UpdateCollectionEfficiencyAsync(
            e.LoanId, e.Amount, e.DueAmount);
    }

    public async Task GenerateDailyReportAsync(DateTime date)
    {
        var report = new DailyReport
        {
            Date = date,
            NewRegistrations = await _analyticsRepo
                .GetCounterAsync("registrations", date),
            LoansDisbursed = await _analyticsRepo
                .GetCounterAsync("originations", date),
            AmountDisbursed = await _analyticsRepo
                .GetMetricSumAsync("originations", date),
            CollectionsReceived = await _analyticsRepo
                .GetMetricSumAsync("collections", date),
            DefaultAlerts = await _analyticsRepo
                .GetCounterAsync("defaults", date),
            InvestorPayouts = await _analyticsRepo
                .GetMetricSumAsync("payouts", date)
        };

        await _analyticsRepo.SaveDailyReportAsync(report);
        await SendReportToStakeholdersAsync(report);
    }
}

23. Cost Estimation

Understanding the cost structure of a P2P lending platform is essential for financial planning and investor communication. The cost of running the platform includes infrastructure costs (cloud computing, databases, storage), third-party integration costs (KYC verification, credit bureau pulls, payment gateway fees), operational costs (recovery agents, customer support, compliance), and technology costs (development, maintenance, security audits). These costs must be recovered through platform fees while keeping the service competitive enough to attract both borrowers and investors.

Infrastructure Cost Breakdown (Monthly)

ComponentSpecificationMonthly Cost
Application Servers (3x)4 vCPU, 16GB RAM15,000 INR
PostgreSQL Primary8 vCPU, 64GB RAM, 1TB SSD25,000 INR
PostgreSQL Replica8 vCPU, 64GB RAM, 1TB SSD25,000 INR
Redis Cluster (3x)16GB RAM each12,000 INR
Elasticsearch (5x)16 vCPU, 64GB RAM, 500GB SSD40,000 INR
Kafka Cluster (3x)4 vCPU, 16GB RAM12,000 INR
Object Storage (S3)5TB5,000 INR
CDN500GB bandwidth3,000 INR
Monitoring (Datadog)Full stack15,000 INR
SSL CertificatesWildcard1,000 INR
Total Infrastructure153,000 INR

Third-Party Integration Costs (Monthly)

ServiceVolumeCost per UnitMonthly Cost
KYC Verification25,000 checks15 INR3,75,000 INR
Credit Bureau Pull9,000 pulls50 INR4,50,000 INR
SMS Gateway5,00,000 messages0.20 INR1,00,000 INR
Email Service2,00,000 emails0.10 INR20,000 INR
NACH Processing2,40,000 debits2 INR4,80,000 INR
E-Signature4,500 signatures8 INR36,000 INR
Document Storage5TB2 INR/GB10,000 INR
Total Third-Party14,71,000 INR

Revenue Model

With 54,000 loans originated annually at an average loan size of 2,00,000 INR, the total annual originations are approximately 108 crore INR. The borrower processing fee at 2% generates 2.16 crore INR annually. The investor commission at 1% of all outstanding portfolio generates approximately 3.6 crore INR annually (assuming an average outstanding portfolio of 36 crore INR). Additional revenue from late fees, prepayment penalties, and premium features adds approximately 50 lakh INR annually. The total annual revenue is approximately 6.26 crore INR against an annual operating cost of approximately 2 crore INR, resulting in a healthy operating margin of approximately 68%.

24. Testing Strategy

Testing a P2P lending platform requires extreme rigor because software defects can lead to financial losses, regulatory violations, and loss of investor trust. The testing strategy must cover unit tests for business logic, integration tests for external services, end-to-end tests for critical workflows, load tests for performance validation, and chaos tests for resilience verification. Every financial calculation must have comprehensive test coverage, and every edge case must be explicitly tested.

Testing Layers

LayerScopeTarget CoverageTool
Unit TestsIndividual services and functions90%xUnit + Moq
Integration TestsService interactions with real DB80%Testcontainers
Contract TestsAPI contract verification100%Pact
E2E TestsComplete user workflowsCritical pathsSelenium + Playwright
Load TestsPerformance under loadP99 < 500msk6
Chaos TestsFault injectionKey failure modesChaos Monkey
Security TestsVulnerability scanningOWASP Top 10OWASP ZAP

Financial Calculation Tests

public class EmiCalculationTests
{
    [Fact]
    public void CalculateEmi_StandardLoan_ReturnsCorrectAmount()
    {
        // Arrange
        decimal principal = 100000m;
        decimal annualRate = 12m;
        int tenureMonths = 12;

        // Act
        decimal emi = EmiCalculator.Calculate(
            principal, annualRate, tenureMonths);

        // Assert - using standard EMI formula
        // EMI = P * r * (1+r)^n / ((1+r)^n - 1)
        decimal expected = 8792m;
        Assert.Equal(expected, Math.Round(emi, 0));
    }

    [Fact]
    public void CalculateEmi_ZeroRate_ReturnsPrincipalDividedByTenure()
    {
        decimal emi = EmiCalculator.Calculate(120000m, 0m, 12);
        Assert.Equal(10000m, emi);
    }

    [Fact]
    public void EmiSchedule_TotalPrincipalEqualsLoanAmount()
    {
        var schedule = EmiCalculator.GenerateSchedule(
            100000m, 12m, 12);

        decimal totalPrincipal = schedule.Sum(e => e.PrincipalPart);
        Assert.Equal(100000m, Math.Round(totalPrincipal, 2));
    }

    [Fact]
    public void EmiSchedule_AmountsNeverNegative()
    {
        var schedule = EmiCalculator.GenerateSchedule(
            50000m, 18m, 24);

        Assert.All(schedule, e =>
        {
            Assert.True(e.PrincipalPart > 0);
            Assert.True(e.InterestPart > 0);
            Assert.True(e.TotalAmount == e.PrincipalPart + e.InterestPart);
        });
    }
}

public class EscrowReconciliationTests
{
    [Fact]
    public void Reconcile_MatchingBalances_ReturnsReconciled()
    {
        var dbBalance = 5000000m;
        var bankBalance = 5000000m;

        var result = Reconciler.Check(dbBalance, bankBalance);
        Assert.True(result.IsReconciled);
        Assert.Equal(0m, result.Difference);
    }

    [Fact]
    public void Reconcile_SmallDifference_WithinThreshold()
    {
        var dbBalance = 5000000m;
        var bankBalance = 4999999.99m;

        var result = Reconciler.Check(dbBalance, bankBalance);
        Assert.True(result.IsReconciled);
    }

    [Fact]
    public void Reconcile_LargeDifference_FlaggedAsCritical()
    {
        var dbBalance = 5000000m;
        var bankBalance = 4500000m;

        var result = Reconciler.Check(dbBalance, bankBalance);
        Assert.False(result.IsReconciled);
        Assert.Equal(AlertSeverity.Critical, result.AlertLevel);
    }
}

Critical Test Scenarios

ScenarioExpected BehaviorPriority
Double investment attempt (idempotency)Second attempt returns existing investment, no duplicateCritical
EMI collected but investor payout failsRetry payout, maintain EMI as collected, alert opsCritical
Escrow balance goes negativePrevented by balance check, transaction rejectedCritical
Credit bureau API timeoutQueue for retry, do not approve loan without bureau dataCritical
Concurrent investment in same loanPessimistic lock prevents oversellingHigh
NACH file generation with holidaysSkip holiday dates, adjust collection calendarHigh
Prepayment mid-monthCalculate exact interest up to prepayment date, adjust EMIHigh
Investor sells loan part mid-EMI cycleCurrent EMI goes to seller, next EMI to buyerHigh

25. Interview Q&A

Q1: How do you handle the case where a borrower pays early (prepayment) and the loan is partially funded by multiple investors?

When a borrower prepays, the outstanding principal must be reduced proportionally across all investors. The system calculates the exact principal owed to each investor based on their investment share, processes the prepayment through the escrow account, credits each investor's wallet, and adjusts the remaining EMI schedule using a reducing balance method. The key challenge is that the EMI schedule must be recalculated in real-time, and investors must be notified of the changed cash flow projections on their dashboard. The prepayment must also update the credit bureau record to reflect the reduced outstanding amount.

Q2: How do you ensure that investor funds in escrow are never mixed with platform funds?

The escrow account is maintained with a scheduled commercial bank as a trust account separate from the platform's operating bank account. All investor deposits go directly into the escrow account, and disbursements to borrowers originate from the escrow account. The platform never has direct access to the escrow funds. The escrow trustee (bank) processes instructions from the platform but only for valid loan disbursements and investor withdrawals. A daily reconciliation compares the platform's internal ledger with the bank's escrow statement, and any discrepancy triggers an immediate alert to the compliance team and the RBI reporting officer.

Q3: What happens if the platform goes down during a batch repayment processing cycle?

The batch repayment processing is designed to be idempotent and resumable. Each NACH file submission is tracked with a unique batch ID, and each individual debit is tracked with an idempotency key. If the system crashes mid-batch, on restart it checks the status of all submitted debits via the NACH gateway API, processes the results for completed debits, and resubmits only the debits that were not confirmed. No EMI is charged twice because each debit submission includes the idempotency key. The recovery process takes at most 30 minutes and requires no manual intervention.

Q4: How do you handle the scenario where an investor tries to invest more than the RBI-permitted limit for a specific borrower?

The compliance check runs synchronously during the investment flow. Before accepting any investment, the system queries the credit bureau to check the investor's total exposure to that borrower across all P2P platforms. If the investment would push the investor over the 10 lakh limit, the system rejects the transaction with a clear error message. For auto-invest rules, the compliance check is built into the rule evaluation engine so that no auto-investment can breach the limit. The system maintains an internal cache of exposure data that is refreshed daily from the bureau, and any investment above 8 lakh triggers a real-time bureau check for the most current data.

Q5: Design the data model for tracking every dollar in the system with full audit trail

Every financial event creates an immutable entry in the ledger table with a sequential transaction ID, timestamp, account IDs involved, debit amount, credit amount, running balance, and a SHA-256 hash of the previous entry (similar to blockchain). The ledger cannot be updated or deleted, only appended. A background job runs hourly to recompute balances from the ledger and compare with the current balance table. Any discrepancy halts new transactions on the affected account and alerts the operations team. The ledger is stored in a separate database with restricted access so that even application administrators cannot modify historical entries.

Q6: How would you handle a situation where the credit bureau API is down and a borrower needs a loan urgently?

The credit bureau API has a 99.9% SLA, but outages do happen. The system implements a multi-layer resilience strategy. First, we maintain a local cache of the borrower's last bureau pull (refreshed every 30 days). If the cache is fresh enough, we use it. If the cache is stale, we have a secondary bureau provider (CRIF or Experian) as a fallback. If both bureaus are down, we enter a degraded mode where loans above 2 lakh are queued for manual review and loans below 2 lakh use the platform's internal scoring model based on alternative data only. No loan is approved without at least one credit assessment source, and all degraded-mode approvals are flagged for retrospective bureau verification when the API recovers.

Q7: Explain the difference between NACH auto-debit failure due to insufficient funds vs technical failure. How does the system handle each?

Insufficient funds means the borrower's bank account does not have enough balance to cover the EMI. Technical failure means the NACH system itself had an issue (network error, bank gateway timeout, file format error). For insufficient funds, the system schedules automatic retries on Day 3, Day 7, and Day 14, sends progressively urgent SMS and email reminders, and starts the dunning process if all retries fail. For technical failures, the system resubmits the same debit instruction on the next business day without any impact on the borrower's record or notification, since the failure is not the borrower's fault. The distinction is critical because treating a technical failure as a borrower default would be incorrect and potentially violate fair lending practices.

Q8: How do you calculate XIRR for an investor's portfolio when they have made partial investments and received partial repayments?

XIRR (Extended Internal Rate of Return) is calculated by finding the discount rate that makes the net present value of all cash flows equal to zero. The cash flows include all investments (negative values representing money going out), all repayments received (positive values), and the current estimated value of outstanding loans (positive). The XIRR calculation uses Newton-Raphson numerical method to solve for the rate. The system must handle edge cases like the investor having only outflows (no repayments yet), very small time differences between transactions, and negative XIRR when defaults exceed returns. The XIRR is recalculated daily and cached for the dashboard, with a background job updating the investor's overall portfolio XIRR and individual loan XIRR.

Q9: Design the system to handle a scenario where RBI changes regulations mid-operation (e.g., reduces interest rate cap from 24% to 18%)

Regulatory changes are managed through a configuration-driven approach. The platform stores regulatory parameters in a versioned configuration table with effective dates. When the RBI announces a rate cap change, the compliance team updates the configuration with the new rate and the effective date. New loans immediately apply the new cap. Existing loans continue under their original terms since the change is not retroactive. The system generates a report of all loans that would have been affected if applied retroactively, which the compliance team uses for regulatory correspondence. The configuration change also triggers a recalculation of all auto-invest rules to ensure they remain valid under the new regulations.

Q10: How do you prevent a single point of failure in the payment collection system?

The payment collection system is designed with multiple layers of redundancy. The NACH gateway has a primary and backup provider. The UPI mandate system is an alternative collection channel if NACH is unavailable. The batch processor runs on a dedicated server with automatic failover to a standby instance. If both the primary and backup NACH providers are down, the system can generate manual payment links via UPI for borrowers to self-pay. The escrow reconciliation runs independently of the collection pipeline, so a collection failure does not affect fund safety. The entire collection pipeline is idempotent, meaning it can be safely restarted from any point without duplicating transactions.

Q11: Explain how you would handle a scenario where two investors try to auto-invest in the same loan simultaneously and the loan only has capacity for one more investment.

This is a classic race condition. The solution uses distributed locking. When the auto-invest engine processes a loan, it acquires a Redis-based distributed lock on the loan ID. The lock has a 30-second TTL to prevent deadlocks. The first engine instance to acquire the lock processes all matching rules for that loan. The second instance, when it tries to acquire the lock, either waits for the lock to be released (with a timeout) or skips the loan if it's already fully funded. Within the lock, the system re-reads the remaining funding amount from the database to ensure it has not changed since the lock was acquired. This optimistic-within-pessimistic approach prevents both double-spending and deadlocks.

Q12: How do you handle platform fees when the investor's share of EMI includes both principal and interest?

The platform fee is charged only on the interest component, not on the principal. When an EMI is collected, the system splits it into principal and interest parts based on the amortization schedule. The platform fee is calculated as a percentage of the interest part (e.g., 1% of interest). The net investor payout is (principal share + interest share - platform fee). The platform fee is accumulated in a separate ledger account and settled monthly to the platform's operating account through a scheduled transfer. The investor's tax statement (Form 26AS integration) reflects the gross interest earned before platform fees, since the platform fee is the investor's expense, not income.

Q13: Describe how you would migrate a running P2P lending platform from a monolithic architecture to microservices without any downtime.

The migration follows the Strangler Fig pattern. We identify bounded contexts within the monolith and extract them one at a time. First, we add an API gateway in front of the monolith that routes requests. Then we extract the least critical service (like notification) into its own microservice, with the gateway routing notification endpoints to the new service and everything else to the monolith. We use the strangler proxy to progressively route more endpoints. Database migration uses the outbox pattern: both the monolith and the new service write to the same database initially, and a change data capture pipeline syncs data to the new service's database. Once the new service is stable, we switch reads to the new database and decommission the old code path. Each migration step can be rolled back independently. The entire migration takes 6 to 9 months for a platform of this complexity.

Q14: How would you implement real-time fraud detection without adding latency to the loan application flow?

The fraud detection system operates in two modes: synchronous pre-screening and asynchronous deep analysis. During loan application, a lightweight rule engine (running in-memory with Redis) checks approximately 20 rules in under 50 milliseconds. These rules include duplicate PAN/Aadhaar, device fingerprint velocity, IP reputation, and basic income consistency checks. If the pre-screening passes, the application proceeds immediately while a background job performs deeper analysis including graph-based collusion detection, bank statement anomaly analysis, and cross-platform exposure checks. If the deep analysis flags the application after it has been approved, the loan disbursement is frozen and the compliance team is notified. This approach keeps the application latency low while catching sophisticated fraud that simple rules cannot detect.

Q15: What are the key metrics you would monitor to detect platform health issues before they impact users?

Infrastructure metrics include CPU utilization above 80% for 5 minutes, memory usage above 85%, disk IOPS saturation, and network latency between services above 10ms. Application metrics include API response time P99 above 500ms, error rate above 0.1%, queue depth above 1000 messages, and job execution time exceeding SLA. Business metrics include default rate trending upward for 3 consecutive days, collection efficiency dropping below 95%, KYC approval time exceeding 24 hours, loan funding time exceeding 72 hours, and investor withdrawal processing time exceeding 4 hours. Each metric has three alert levels: warning (page the on-call engineer), critical (page the engineering lead), and emergency (page the CTO and trigger incident response). All alerts must be actionable and include a runbook link with resolution steps.

Conclusion

Designing a P2P lending platform is a comprehensive exercise that spans the full spectrum of system design challenges: from credit assessment and risk modeling to escrow fund management and regulatory compliance, from real-time auction mechanisms to batch repayment processing, from fraud detection to investor dashboards. The platform sits at the intersection of technology, finance, and regulation, requiring engineers to think not just about scalability and performance, but also about correctness, auditability, and compliance.

The key takeaways from this design are: financial data requires strong consistency and cannot tolerate eventual consistency for balances or transaction records, regulatory compliance must be embedded into the system architecture rather than bolted on as an afterthought, the escrow system is the trust backbone of the platform and must be implemented with zero tolerance for discrepancies, credit assessment accuracy directly impacts investor returns and platform reputation, and the system must be designed for resilience because financial transactions cannot be interrupted by infrastructure failures.

In a system design interview, the P2P lending platform question gives you the opportunity to demonstrate depth in multiple domains. Start with the core lending lifecycle (application, assessment, listing, funding, disbursement, repayment), then expand into the supporting systems (KYC, credit scoring, escrow, notifications), and finally discuss the operational aspects (compliance, monitoring, testing). The candidate who can navigate between high-level architecture and low-level implementation details while keeping the regulatory context in mind will stand out in any senior or staff-level interview.

© 2026 Ayodhyya. All rights reserved. | Design a Peer-to-Peer Lending Platform: The Complete Guide — A Senior+ Guide