About the Platform
The complete picture in one place
👥 Two User Worlds
Customers interact via a web/mobile app (handled by the /web/ API). Partner staff (loan executives, collection agents, admins) interact via the React partner portal (the frontend we documented). These are completely separate authenticated systems.
🏷️ Multi-Brand Platform
A single codebase serves multiple lending brands simultaneously: Qualoan, PaisaPop, Salary4Sure, FastSalary, SalaryBolt, and more. Each brand has its own loan rules, theme, communication templates, staff, and settings — but shares the same infrastructure.
🔄 Full Lifecycle Coverage
From customer acquisition (UTM tracking, lead forms) → onboarding (KYC) → credit assessment (BRE, CIBIL) → approval → e-sign → disbursement → repayment → collections → closure. Every step is tracked, logged, and auditable.
Full System Architecture
Every layer of the platform and how they connect
Client Layer
API Layer — NestJS 10 on Port 4002 (PM2 Cluster × 2)
Auth & Identity
Loan Engine
Payment Engine
Database
Cache & Queues
File Storage
External API Integrations (25+)
Multi-Brand Model
How one codebase serves many lending brands
/:brandId/loans, /:brandId/dashboard, etc. The backend extracts this brandId from the URL, validates that the logged-in partner user has access to that brand, and then filters all database queries by that brandId. Two staff at different brands cannot see each other's data even if they use the same app.
🎨 Brand Isolation
Each brand has its own: logo, color theme, loan rules (min/max amounts, tenures, interest rates), evaluation criteria, email templates, SMS templates, partner staff, and customers. Complete data isolation at the database level via brandId foreign keys.
⚙️ Brand Config Flow
On login, the partner portal fetches brand config via GET /settings/brand. This is stored in the Redux brand slice. The ThemeProviderContext injects the brand's colors as CSS variables. All subsequent API calls include the brandId automatically.
🔐 Brand Access Control
A partner user can be assigned to one or multiple brands (stored in PartnerUserBrandRole). They log in once and can switch between their brands. The Super Admin can access all brands via the /admin portal without brand scoping.
Complete Customer Journey
Every step from signup to loan closure
KYC Verification Flow
Detailed flow of every identity verification step
PAN Verification
Customer enters PAN number → Frontend calls POST /web/kyc/verify-pan → Backend calls Digitap PAN API → Name, DOB fetched from PAN database → Saved to PanAadhaarVerification table → Customer's name pre-filled
Aadhaar OTP Verification
Customer enters Aadhaar number → POST /web/kyc/initiate-aadhaar-otp → Digitap sends OTP to Aadhaar-linked mobile → Customer enters OTP → POST /web/kyc/submit-aadhaar-otp → Address + photo extracted from Aadhaar → Saved to User model
Document Upload
Customer uploads PAN card image, Aadhaar image, selfie → React Dropzone sends multipart form → Backend calls S3Service.uploadPrivate() → Returns S3 key → Creates Document record with type, S3 key, status=PENDING → Partner staff reviews and marks VERIFIED
Mobile Verification
Verify that phone number matches PAN records → Mobile-to-PAN API check → Also verifies address from mobile number via Mobile-to-Address provider → Saved to MobileToPanVerification
DigiLocker (Optional)
For enhanced verification, customer authorizes DigiLocker 2.0 → Official documents fetched directly from government → Stored in aadhaar_digi_locker_log → Bypasses manual document review
Bank Account + Penny Drop
Customer enters account number + IFSC → Backend calls Penny Drop API (sends ₹1 to account) → Verifies account holder name matches PAN name → Creates UserBankAccount with verified=true
Employment via EPFO
Customer enters UAN → Phone-to-UAN API verifies UAN belongs to customer's phone → UAN-to-Employment API fetches employment history from EPFO → Employer, salary, joining date extracted → Creates Employment record
Loan Application Flow
From application submission to allocation
📋 Application Submission
Customer submits loan application
Enters desired loan amount and tenure on the web app. Frontend validates against brand's min/max rules before submitting.
Brand rules validation
Backend's brandRuleValidation service checks amount, tenure against brand config. Rejects if outside bounds.
Loan record created
Creates Loan record with status=APPLIED. Saves amount, tenure, userId, brandId.
Auto-allocation triggered
autoAllocation service assigns loan to an available loan executive using round-robin + workload balancing.
📊 Bank Statement Analysis
Customer uploads bank statement
PDF uploaded to S3 private bucket. S3 key saved to BankAccountStatement.
BSA provider called
Statement sent to CART, Finbox, or ScoreMe depending on brand config. Provider analyzes salary credits, EMI debits, balance trends.
BDA report stored
Analysis results saved to BdaReport. Available in customer detail view for loan executives to review.
Account Aggregator (optional)
RBI AA consent flow — customer authorizes bank data sharing directly. More accurate than PDF analysis.
Credit Assessment Flow
How the BRE and evaluation engine decide on a loan
Sanction Approval Flow
How loan approval moves through internal hierarchy
E-Signature Flow
Digital agreement signing via Signzy / SignDesk
Agreement PDF Generated
Backend uses PDFKit/Puppeteer to generate a loan agreement PDF with customer details, loan amount, tenure, interest rate, repayment schedule, T&C. Saved to S3 private bucket.
E-Sign Request Sent
Backend calls Signzy V3 or SignDesk API (based on brand config) with the PDF URL. Provider sends a signing link to the customer's registered mobile/email.
Customer Signs
Customer opens the link, reviews the agreement, signs with Aadhaar OTP-based e-signature. The signature is legally valid under India's IT Act.
Webhook Confirmation
Signzy/SignDesk sends a webhook to the backend with signing status. Backend updates Loan.agreementStatus = SIGNED, saves signed PDF S3 key, triggers disbursement workflow.
Disbursement Flow
From signed agreement to money in customer's account
Collection Flow
Repayment management from pre-due to closure
📞 Acefone VoIP Integration in Collection
When a collection executive clicks "Call" on a loan in the portal, the AcefoneContext in the frontend initiates a VoIP call via the Acefone API. The call widget appears as an overlay. Backend creates a UserCall record. Call events (connected, ended, etc.) trigger UserCallEvent records. After the call, the recording URL is saved and linked to the customer's profile in the UserCallRecording table. Collection agents can replay recordings directly from the portal.
Payment Processing Flow
How money flows through the system
Payment Initiated
Customer pays via Razorpay/Cashfree payment page (from customer app). OR collection agent triggers payment link from portal. Creates PaymentRequest record with PENDING status.
Gateway Processing
Payment gateway processes the transaction. Customer authorizes via UPI, card, or netbanking. Gateway holds the transaction in PROCESSING state.
Webhook Callback
Gateway sends a webhook to POST /partner/brand/:brandId/payment/webhook. Backend validates webhook signature. If SUCCESS: creates PaymentCollectionTransaction, updates Loan.outstandingAmount, marks EMI as paid in RepaymentTimeline.
Approval Queue (if applicable)
Certain payment types require manual approval. Loan Ops staff see these in the /:brandId/payment-approval queue. They review UTR number, amount, and approve or reject.
Ledger + Notifications
Creates LoanLedgerEntry for financial tracking. Sends payment receipt via SMS + email. If all EMIs paid: triggers loan closure flow → NDC generation → NOC email sent via SQS queue.
Authentication Flow (Both Sides)
Customer OTP auth vs Partner password auth
Customer Auth (OTP-based)
Enter phone number → POST /web/auth/register
Backend generates 6-digit OTP → saves to UserOtpVerification → sends via SMS (MSG91/Gupshup)
Enter OTP → POST /web/auth/verify-otp → OTP validated (5min TTL)
JWT issued → UserLoginToken created → tokens returned to customer app
Partner Auth (Password-based)
Enter email + password on login page
Backend fetches PartnerUser by email → bcrypt.compare(password, hash)
Device fingerprint registered → PartnerUserLoginLog created
JWT issued with roles+permissions+brandIds embedded → PartnerLoginToken stored → Redux updated
Notification & Communication Flow
How messages reach customers and staff
UserNotification recordAuto-Allocation Flow
How loans and customers are automatically assigned to staff
👤 Customer Auto-Allocation
When a new customer completes registration, user.autoAllocation.service finds an available loan executive by: checking brand assignment, current load, availability status. Creates LoanAllottedPartnerUser record. Executive gets notified.
📋 Loan Auto-Allocation
When a loan application is submitted, loan.autoAllocation.service assigns it using round-robin rotation among loan executives with capacity. Checks PartnerUnavailabilityDate to skip staff on leave.
💼 Collection Auto-Allocation
When a loan becomes overdue, collection.autoAllocation.service assigns it to a collection executive based on: geographic proximity, current collection load, and performance ratings from partner_user_rating_matrix.
Complete Data Flow Map
Where data comes from and where it goes
📥 Inbound Data Sources
📤 Outbound Data Flows
Roles × Features Access Matrix
Who can access what in the partner portal
| Feature | Super Admin | Admin | Loan Exec | Credit Exec | Sanction Mgr | Sanction Head | Coll. Exec | Coll. Mgr | Coll. Head |
|---|---|---|---|---|---|---|---|---|---|
| Dashboard | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Customer List | ✓ | ✓ | ✓ | ✓ | — | — | — | — | — |
| Loans List | ✓ | ✓ | ✓ | ✓ | — | — | — | — | — |
| Sanction Manager View | ✓ | ✓ | — | — | ✓ | ✓ | — | — | — |
| Credit Executive View | ✓ | ✓ | — | ✓ | — | — | — | — | — |
| Collections | ✓ | ✓ | — | — | — | — | ✓ | ✓ | ✓ |
| Pre-Collection | ✓ | ✓ | — | — | — | — | ✓ | ✓ | ✓ |
| Write-Off Authority | ✓ | ✓ | — | — | — | — | — | — | ✓ |
| Loan Ops | ✓ | ✓ | — | — | — | — | — | — | — |
| Settings | ✓ | ✓ | — | — | — | — | — | — | — |
| Reports | ✓ | ✓ | — | — | — | — | — | ✓ | ✓ |
| Staff Management | ✓ | ✓ | — | — | — | — | — | — | — |
Integrations Map
Which external API is used at which stage
🆔 KYC Stage
📊 Credit Assessment Stage
✍️ Approval & eSign Stage
💸 Disbursement Stage
💳 Repayment/Collection Stage
📱 Communication (All Stages)
Security Layers
Every security mechanism in the platform
🔑 Authentication
🛡️ Authorization
🌐 Network Security
📦 Data Security
📱 Device & Session
📝 Audit & Compliance
Performance Architecture
How the platform handles scale
🚀 Frontend Performance
⚡ Backend Performance
📊 Database Performance
🔄 Async Performance
🏁 Summary — The Complete 8byte LOS Platform
This platform is a full-stack, production-grade loan origination system that covers every aspect of the Indian NBFC lending process:
The React frontend and NestJS backend are tightly coupled via a well-defined REST API, with JWT authentication, brand scoping, and role-based access enforced at every layer — from the Redux store and React Router guards in the frontend, all the way down to Prisma database queries filtered by brandId on the backend.
Account Aggregation (AA) Flow
RBI-framework consent-based financial data fetch via FINDUIT / CART providers
Credit Executive Initiates AA
From the loan detail page, a credit exec clicks "Initiate Account Aggregation". The frontend calls POST /partner/brand/:id/aa/consent-request with userId + loanId. Backend creates an AAConsentRequest record with status PENDING.
Consent Link Sent to Customer
Provider (FINDUIT/CART) generates a consent URL and sends it to the customer via SMS/WhatsApp. The AA consent record stores consentId, txnId, and the redirect URL for tracking.
Customer Approves Consent
Customer opens the link on their phone, authenticates with their bank's AA app (e.g. PhonePe, Perfios), and approves sharing 6–12 months of bank statement data. Consent status updates to APPROVED via webhook.
Data Fetch & Analysis
Backend automatically fetches the financial data from the AA provider once consent is approved. The data is stored and the credit exec can view the Bank Statement Analysis (BSA) report inside the loan profile — salary credits, EMI obligations, spending patterns.
Credit Assessment Integration
AA data feeds into the CAM Calculator (average salary, obligations) and the overall credit decision. The consent request list is available via GET /partner/brand/:id/aa/consent-requests with pagination and status filtering.
Lead Management Flow
Facebook Ad leads → CSV import → match to users → auto-process
CSV Upload
Admin or Sales team downloads the leads CSV from Facebook Ads Manager and uploads it via POST /partner/brand/:id/lead-forms/upload (multipart/form-data). Backend parses rows and stores each as a LeadForm record with status PENDING.
Deduplication Check
On upload, the system checks for duplicate entries (same phone/email within the same brand). Duplicates are marked with status DUPLICATE and excluded from further processing.
Sync / Process
Staff trigger sync via POST /partner/brand/:id/lead-forms/sync. The backend attempts to match each PENDING lead to an existing platform user by phone number. Matched leads get PROCESSED status; unmatched or errored leads get FAILED with an error message.
View & Filter
The Leads page shows paginated lead forms with search, status filter (PENDING / PROCESSED / FAILED / DUPLICATE), and stats cards (total / pending / processed / failed / duplicates). Staff can drill into each lead to see campaign info (ad ID, ad set, form name) and lead responses (salary, PAN, employment type).
Export & Bulk Delete
Leads can be exported as CSV (GET /lead-forms/export) for external use. Bulk delete is available via DELETE /lead-forms/bulk with an array of IDs. Organic leads (isOrganic=true) are tracked separately from paid campaign leads.
CAM Calculator Flow
Credit Appraisal Memo — how credit executives assess loan eligibility
Open CAM Form
Credit executive opens the loan profile and navigates to the CAM Calculator tab. The frontend fetches any existing CAM via GET /loans/cam-calculator/:loanId — if one exists, the form is pre-populated.
Enter Salary Data
Exec enters up to 3 months of salary credit dates and amounts (salaryCreditDate1-3, salaryAmount1-3). The system auto-calculates avgSalary. The next pay date (nextPayDate) is also captured for disbursement planning.
Compute Eligibility
Based on avg salary and existing obligations (obligations), the system computes: eligibleFoir (allowed FOIR%), foirAchieved (current FOIR%), proposedFoir (after new loan), eligibleLoan (max loan based on FOIR), and roi (rate of interest).
Set Recommended Amount
Exec enters loanRecommended (which may differ from the customer's requested loanApplied). Disbursal date, repayment date, and tenure are also set. The repaymentData JSON captures the full EMI/repayment schedule.
Save & Submit
On save, the frontend calls POST /loans/brand/:brandId/cam-calculator/save. Backend upserts the CAMCalculator record linked to loanId + userId + partnerUserId. The saved CAM becomes part of the loan file reviewed by sanction managers.
Reminder Scheduling Flow
How staff create, schedule, and track customer reminders
Create Reminder
Staff opens the Reminders page and clicks "Create Reminder". They select a user (customer), choose a channel (SMS / WhatsApp / Email), pick a message template (templateCode), set a future scheduledAt timestamp, and optionally fill template variables in the payload map.
Store in DB
The frontend calls POST /partner/brand/:id/user-reminders. Backend creates a UserReminder record with status PENDING. The record stores userId, channel, templateCode, scheduledAt, and payload.
Cron Job Dispatch
A background cron runs every minute (@Cron(CronExpression.EVERY_MINUTE)) and polls for reminders where scheduledAt ≤ now and status = PENDING. It dispatches the message via the appropriate provider (MSG91, Gupshup for SMS; AiSensey for WhatsApp; SendGrid for Email) using the template code.
Status Update
After dispatch attempt, status updates to SENT (success) or FAILED (error). If a providerMessageId was specified during creation, it's stored for delivery tracking. Every status change is audit-logged.
Monitor & Audit
Staff views all reminders via GET /partner/brand/:id/user-reminders with filters: channel, status, date range, search. The Audit Log for each reminder (GET /user-reminders/:id/audit-logs) shows every state transition with timestamps and actor.
Acefone VoIP Click-to-Dial Flow
End-to-end flow: staff initiates a call to a customer from the partner portal
Brand Enables Dialer
Admin enables dialer via Brand Settings → Acefone configuration (API credentials, extension numbers). The AcefoneDialerProvider in the React app wraps the entire application and initializes the dialer SDK on mount.
Staff Clicks Call Button
On any user/loan profile page, staff clicks the phone icon next to the customer's number. The useAcefoneDialer hook calls initiateCall() which posts to /acefone/dialer/initiate with userId, partnerUserId, brandId, and optional loanId.
Acefone API Call
The backend forwards the click-to-call request to Acefone's API using the configured credentials. Acefone bridges the call — first calling the agent's extension, then dialing the customer when the agent answers.
Call Logs Auto-Saved
After the call ends, Acefone sends a webhook to POST /acefone/dialer/outbound. Backend stores the call log — duration, call status (answered/missed/busy), recording URL — linked to the user/loan.
View History
Call logs are viewable per user (GET /acefone/dialer/user/:userId/calls) and per partner agent (GET /acefone/dialer/partner/:partnerUserId/calls). Individual call details via GET /acefone/dialer/call/:callId. Stats per user/agent via the /stats endpoints.
Report Generation Flow
How reports are generated, what types exist, and how they're exported
📋 User & Loan Reports
master-report — full user+loan dump (PAN, Aadhaar, employment, bank, references, all loan statuses).
disbursed-loan-report, non-disbursed-loan-report, disburse-non-disburse-report — disbursement tracking.
active-loans-by-due-date-report, completed-loan-with-no-repet-report, loan-close-report, outstanding-data-report, reject-report, total-approve-sanction-report, affiliate-report, login-sessions-report.
💼 Collection Reports
master-collection-report — overdue loans, amounts, agent assignments.
collection-loan-report, collection-due-report, collection-loan-report-by-approved-date — per-loan collection views.
collection-allocation-executive-report, credit-executive-allocation-report, collection-remarks-report, total-recovery-report, field-visit-report.
📣 Marketing & Credit Reports
marketing-report, internal-marketing-report, internal-marketing-analysis-report — lead funnel and campaign data.
daily-marketing-mis-report — daily MIS snapshot.
lead-total-report, cic-report (CIBIL/Equifax bureau pulls), equifax-credit-report, transunion-report.
Select Report Type & Date Range
Admin opens the Reports page, selects report type from dropdown (e.g. master-report, marketing-report, collection-loan-report, disbursed-loan-report), and sets fromDate / toDate. There are 29 report types total.
Fetch Data
Frontend calls GET /partner/brand/:id/report/:reportType?fromDate=&toDate=. Backend queries the DB with date filters and returns typed arrays — MasterReportUser[], MarketingReportItem[], CompletedNoRepaymentReportItem[], or BaseReportItem[] for other types.
Display & Export
Data renders in a table. Staff can export as CSV via GET /report/:reportType/csv or download via GET /report/:reportType/download. Both return a stream — browser triggers file download. The master report also supports POST /master-report/email for email delivery of large datasets.