Skip to content

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.

01

Onboarding

Borrower + sponsor pair created mechanism.user.added

02

Identity

PAN + Aadhaar attached to user identity.registered

03

DPDPA consent recorded consent.recorded

04

KYC

PAN validated, Aadhaar Verhoeff kyc.verified

05

AML

Risk score + sanctions screening aml.cleared

06

Credit bureau

CIBIL / Experian / Equifax pull credit_bureau.checked

07

Underwriting

Rule engine + risk model underwriter.approved

08

Pricing

RBI rate caps + all-in-cost APR pricing.computed

09

Compliance

Cooling-off + breach checks kfs.generated

10

KFS

Key Fact Statement issued kfs.generated

11

Mandate

e-NACH / UPI Autopay (v1.0) razorpay.mandate.active

12

Origination

Loan booked, audit persisted loan.originated

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.

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.

create
sign
publish
handle
persist
audit
replay

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.

APPLICATION DOMAIN KYC services/kyc

AML services/aml

Credit services/credit

Pricing services/pricing

KFS services/kfs

TYPED EVENTS kyc.verified · aml.cleared · pricing.computed · kfs.generated

UNDERWRITE CORE Authz policy engine Identity Ed25519 keys Idempotency dedup guard Supervisor auto-restart Sagas + Circuit breaking DLQ + bounded buffer

INFRASTRUCTURE Store · Sqlite

Metrics · Prometheus

Tracing · OTLP

Secrets · 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.

pip install
Runtime()
define service
handle event
compose services
run lending lifecycle

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:

PAN
Aadhaar
Consent
KYC/AML
CIBIL/CKYC
Pricing
KFS
Origination

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.

v0.9 — shipped

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)
v1.0 — future

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.

Architect

View architecture

Runtime, events, control plane

Risk & compliance

Explore compliance

RBI / DPDPA-aligned defaults

Researcher

Inspect implementation

Source on GitHub

Contributor

Contribute

Branching, gates, PR process


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.