Open-source lending infrastructure
Build Indian lending systems on a hardened underwriting runtime.¶
Underwrite turns underwriting capabilities into composable nano-services communicating through typed, signed events. The runtime handles the cross-cutting operational concerns — authz, identity, idempotency, tracing, metrics, sagas, supervision, and dead-lettering — so a 50-line service can carry the guarantees of a regulated platform.
34 nano-services
132 event types
1,276+ tests
Ed25519 event signatures
RBI / DPDPA aligned defaults
The underwriting runtime
Domain logic on top. Runtime guarantees underneath.¶
The product is not the services — it is the runtime they run on. Underwrite is structured so that the four layers below are clearly separated: domain code expresses lending intent, the event runtime guarantees delivery, the control plane enforces policy, and the operational layer makes the system inspectable.
Domain services¶
KYC, AML, credit, pricing, compliance, consent, KFS, origination, and related workflows. These are the parts that change when a lender changes policy.
- 34 wired nano-services
- PAN, Aadhaar eKYC, CIBIL, CKYC integrations
- RBI-aligned pricing and KFS
Event runtime¶
Typed events with Ed25519 attestation, idempotent dispatch, replay, and routing. The runtime decides how events flow; the services decide what they mean.
- 132 typed event types
- 5-minute replay window
- Bounded DLQ with deduplication
Control plane¶
Authorization, identity, saga coordination, supervision, and failure handling. Policies and rules live here, so individual services stay focused on domain logic.
- Default-deny access control
- Saga orchestrator with rollback
- Per-handler circuit breaking
Operational layer¶
Metrics, health, readiness, tracing, persistence, and deployable infrastructure. The runtime exports signals in formats operators already use.
- Prometheus metrics on
/metrics - OpenTelemetry tracing (console / OTLP)
- Sqlite store, PII-redacted audit ledger
The brand idea
You define the lending domain. Underwrite provides the runtime.¶
Complex financial infrastructure should not have to be reinvented inside every service. A Underwrite service looks like a function on an event; everything else is provided automatically and uniformly across the system.
The runtime guarantees are not a feature checklist — they are the product. Authorization, signing, idempotency, tracing, metrics, sagas, supervision, and the dead-letter queue arrive by construction, not by convention.
YOUR CODE ↓ handle(event)
UNDERWRITE RUNTIME ├── authz ├── identity ├── signatures ├── idempotency ├── tracing ├── metrics ├── saga coordination ├── DLQ └── supervision
Indian lending lifecycle
One borrower, twelve stages, every guarantee carried.¶
The full underwriting flow runs end-to-end against an in-memory store. Each stage is a nano-service, each transition is a typed event, and each event carries the runtime's guarantees — signatures, audit, replay — into the next.
Compliance by default
Regulatory concerns, encoded into the runtime.¶
Underwrite's defaults are aligned with RBI Digital Lending Guidelines and DPDPA 2023 — rate caps, all-in-cost APR, penal-interest limits, KFS cooling-off, consent lifecycle, DSR fulfillment, breach notification, and auto-purge. They are properties of the runtime, not conventions every team has to remember to apply.
Use the precise language: RBI / DPDPA-aligned defaults. We do not claim regulatory certification; we publish the configuration and code that encode the controls.
Rate caps¶
Personal loan, education loan, and consumption-loan caps per RBI norms. The pricing service refuses to compute outside the band.
All-in-cost APR¶
APR includes interest, processing fees, GST, and insurance — not just headline interest. The pricing service emits it as a first-class field.
Penal-interest cap¶
Penal interest is bounded as a function of the outstanding principal; the pricing service enforces the cap on every compute.
KFS cooling-off¶
Key Fact Statements are issued before disbursement. The KFS service enforces a cooling-off period between issuance and loan booking.
Consent lifecycle¶
DPDPA consent is recorded with purpose, validity, and withdrawal. The consent service enforces re-consent when the purpose changes.
DSR fulfillment¶
Data Subject Rights requests are tracked end-to-end with response-time SLAs. The DSR service emits breach events on SLA violations.
Breach notification¶
Breach detection, classification, and notification are wired through the runtime. Notifications fire within configured windows.
Auto-purge¶
PII-bearing records are purged at the configured retention horizon. The audit service redacts PII before export.
Provable event history
Every event is signed. Every signature is verifiable. Every record is auditable.¶
An event moves through the runtime as a structured, attestable record. The same bytes that are signed on the publisher are verified by every subscriber, persisted by the audit service, and exposed for replay by the DLQ. The result is an event history that holds up to external review.
Ed25519 signatures¶
Every event carries an Ed25519 signature over the canonical signing bytes — event_id | timestamp | event_type | source | payload. A holder of one trusted key cannot re-stamp an event under another service id or replay an old event outside the configured window.
Replay window¶
A configurable window (default 5 minutes) bounds the lifetime of a signature. Old captures cannot be re-broadcast. Operators tighten the window for high-risk flows and disable it for trusted backplanes.
PII-redacted audit¶
The audit service persists every event after redacting PAN, Aadhaar, and other token-matched identifiers. The same redaction applies to the DLQ and the Prometheus exporter.
Deterministic records¶
Canonical signing bytes use sorted JSON keys, strict JSON serialization (no default=str coercion), and an event-id generated from a UUIDv4. Two processes on different Python versions produce the same signature for the same event.
Architecture
A small core, surrounded by domain services and operational infrastructure.¶
Domain services express lending intent. The Underwrite core enforces the guarantees. The infrastructure layer provides the building blocks that operators already trust — Sqlite, Prometheus, OTLP, Vault, AWS.
The full architecture — including the layered rationale, event lifecycle, and runtime injection points — lives on the architecture page.
Developer experience
From zero to underwriting workflow.¶
Despite the runtime depth, the developer experience is short. Six steps and you have a regulated-grade event flow running locally — no broker, no cluster, no external services required.
The smallest useful example:
$ git clone https://github.com/sachncs/underwrite.git $ cd underwrite && ./setup.sh $ source .venv/bin/activate $ python docs/examples/indian_lending.py seed.added hdfc-bank seeded ₹10,000,000 user.added priya-sharma sponsored by hdfc-bank (₹500,000) consent.recorded kyc_verification consent granted kyc.verified PAN + Aadhaar valid aml.cleared Risk score 1 — cleared ckyc.verify Registry lookup initiated credit_bureau.checked Score: 720 (CIBIL) pricing.computed ₹300K @ 28% APR, EMI ₹16,543/month kfs.generated Key Fact Statement v1.0 issued loan.originated ₹300,000 personal loan approved dlq.size 0
Quickstart
Install on the left. Run the Indian scenario on the right.¶
The setup script creates a virtualenv, installs editable + dev extras, and configures pre-commit hooks. The Indian lending example exercises the full event-driven workflow against an in-memory store — a regulator-aligned origination in a fraction of a second.
1. Install and initialize¶
# clone and bootstrap $ git clone https://github.com/sachncs/underwrite.git $ cd underwrite && ./setup.sh $ source .venv/bin/activate
# verify $ pytest tests/ -q 1276 passed in 6.2s
# initialize a config $ underwrite init
2. Run the Indian scenario¶
# start the runtime services $ underwrite run mechanism audit pricing compliance
# exercise the full borrower flow $ python docs/examples/indian_lending.py loan.originated ₹300,000 personal loan approved
# inspect the audit trail $ underwrite health
Underneath, the run drives the pipeline:
The complete annotated walkthrough lives on the quickstart page.
Infrastructure flexibility
Start inside one Python process. Replace infrastructure as your system grows.¶
Underwrite begins as a single Python process with Sqlite and an in-process bus. The same code runs in production against OTLP tracing, Vault-backed secrets, and multi-arch container images. There is no "lite" version and no "production" version — the runtime is the same; the backends swap.
Local¶
- SQLite store (file or
:memory:) - In-process event bus
- Console tracing
- Env-var secrets
- Single process, in-memory DLQ
Production¶
- Sqlite + optional WAL volume mounts
- Pluggable bus: Modal, SQS, in-process
- OTLP tracing to a collector
- Vault / AWS secrets managers
- Multi-arch Docker image (amd64 + arm64)
- DLQ with store-backed durability
The default extras ([risk], [serve], [otlp], [vault], [aws], [gcs], [modal]) install only the dependencies you opt into. The core stays minimal.
Operator experience
Observability as a product surface, not an afterthought.¶
The runtime emits health, readiness, metrics, traces, DLQ state, and saga state in formats operators already use. No proprietary dashboards; no fake monitoring screenshots; no claims about "visibility" without surfaces.
Health and readiness¶
/healthz (liveness) and /readyz (readiness, including store ping) are wired for Kubernetes probes. /v1/health reports every subsystem.
Metrics¶
Prometheus metrics on the conventional /metrics path with /v1/metrics as a versioned alias. Counters, timers, gauges; per-handler latency and per-event-type counts.
Tracing¶
OpenTelemetry spans for every dispatch. Console by default, OTLP via the [otlp] extra. Parent / child propagation across events.
DLQ replay¶
The dead-letter queue is bounded, deduplicated, and replayable. The underwrite dlq --replay command replays the entire queue back onto the bus.
Saga rollback¶
Multi-step workflows carry compensating actions. The orchestrator emits saga.rolled_back with the failed step; the runtime drives compensation.
Release process¶
Tag-driven publishing with reproducible builds, signed wheels, multi-arch images, and a documented release checklist. See release process.
Product status
What exists today, and what is still ahead.¶
What v0.9 ships¶
- 34 wired nano-services, 132 event types
- Real KYC wire-protocol clients (PAN, Aadhaar, CIBIL, CKYC)
- Ed25519 event signatures with replay window
- Default-deny authz with policy file
- Bounded DLQ with deduplication
- PII-redacted audit ledger
- Prometheus metrics, OpenTelemetry tracing
- Production Docker image (multi-stage, non-root)
- Full CI gate suite (mypy, ruff, bandit, pip-audit, TruffleHog)
What belongs to v1.0¶
- Live KYC partner-sandbox validation
- e-NACH / UPI Autopay mandate collection
- Full RBAC beyond the basic policy file
- Pre-built multi-arch Docker images published to GHCR
- Production on-call runbook (Ed25519 key rotation, DLQ replay, breach notification)
- Video KYC integration (Digilocker, NSDL eSign)
- Saga persistence via the Store backend (in-memory today)
- OpenAPI 3.1 spec generated from the FastAPI surface
A Helm chart is not planned. Deploy the multi-arch container directly or with a project-specific compose / kustomize overlay. Full roadmap: ROADMAP.md.
Where to start
Five visitors, five paths.¶
Each path leads to real documentation, not a marketing dead end.
Underwrite is open source under the MIT license. Inspect the code, run the runtime, deploy it in your own infrastructure. Financial infrastructure you can read, modify, and own.