← All projects
🏦
CompletedIO2i Outsourcing Pvt. Ltd. · 2026

B2B Financial Institution Portal

Multi-tenant ODR dashboard for banks and NBFCs

Python 3FastAPIPydanticHTTPXRustLeptosWebAssemblyDockerNginxHMAC SHA-256

B2C → B2B migration

Architecture

0 case records

Local storage

Cryptographic per-tenant

Isolation

Prometheus + JSON logs

Observability

Where Resolution Desk serves borrowers, this portal serves the other side — the banks, NBFCs, and clients who initiate and manage arbitration proceedings. A fully multi-tenant B2B gateway: institutional users manage large case portfolios with cryptographic data isolation between tenants.

The problem

Multiple financial institutions needed to manage their arbitration portfolios through IO2i's platform — but running separate deployments per institution was operationally unsustainable. A single codebase needed to securely serve N institutions with complete data isolation and no shared state between tenants.

The solution

A dynamic multi-tenant architecture where the application resolves the institution from the incoming host header, loads that institution's credentials from a cryptographically-verified registry, and uses those credentials exclusively for all upstream data calls. Each institution sees only their own data — not because of database-level row filtering, but because the API keys injected into every outbound request are strictly bound to that institution.

Platform Internals

B2B Portal Secure Sign-in

Secure Tenant Portal Sign-in

Resolution Desk OTP

OTP Generated

Institution Operations Dashboard

Arbitration & Litigation Portfolio Summary

Bulk Notice Campaign Analytics

Bulk Notice Campaign Analytics & Channel Performance

Architecture

The Dynamic Tenant Registry (tenant_registry.py) periodically contacts a Central Authority using HMAC SHA-256 timestamped signatures and receives an encrypted payload of all active tenant credentials. FastAPI middleware reads X-Tenant-Host on every request and loads the matching TenantConfig into a Python ContextVar. The lwt_api_client pulls from this context to sign all outbound HTTPX requests to upstream Frappe servers. Prometheus metrics and JSON-structured logs (observability.py) capture every request with a UUID trace ID.

tenant_registry.pypython
async def resolve_request_tenant(request: Request) -> TenantConfig:
    # X-Tenant-Host is sent by the frontend for local dev / tenant selection.
    # Falls back to X-Forwarded-Host (nginx in prod), then the raw Host header.
    host = (
        request.headers.get("x-tenant-host")
        or request.headers.get("x-forwarded-host")
        or request.headers.get("host")
        or ""
    )
    return get_tenant_for_host(host)

def require_current_tenant() -> TenantConfig:
    tenant = _current_tenant.get()
    if tenant is None:
        raise RuntimeError("Tenant context is not set")
    return tenant

Engineering Deep Dive

⚠️

Challenges Faced

Managing multiple financial institutions securely without deploying 10 different instances. We needed absolute data isolation, but passing Tenant API credentials explicitly through every single function down the call stack is messy and error-prone.

🛠️

Techniques Used

Host-based multi-tenancy utilizing Python `ContextVar`. The middleware intercepts the `X-Tenant-Host` header, looks up the tenant config from a dynamically synced in-memory registry, and sets it in a `ContextVar`. The API client reads from this variable automatically when signing outgoing requests.

Why This Technology?

Python `contextvars` is the only safe way to handle global state in an async environment. Unlike thread-locals, `contextvars` are strictly scoped to the async task. Two concurrent requests from different banks cannot accidentally bleed context. I used Leptos (Rust) for the frontend because its fine-grained reactivity handled massive multi-tenant data tables far better than React's Virtual DOM.

Technical deep-dive

The ContextVar pattern is the key architectural insight: Python's contextvars are async-task-scoped, so there is no shared mutable state between concurrent requests. Each request carries its own TenantConfig snapshot for its entire async lifecycle. Two simultaneous requests from different tenants cannot see each other's context — there's no locking, no risk of credential bleed. The HMAC registry sync means credentials can be hot-swapped without a redeploy, which is critical when serving multiple production tenants.

What was built

Host-based multi-tenancy — X-Tenant-Host header resolves institution per request

HMAC SHA-256 signed Central Authority sync — no hardcoded credentials ever

TenantConfig injected into ContextVar — cross-tenant leakage structurally impossible

Prometheus metrics + JSON structured logging via python-json-logger

Request tracing with UUID request IDs propagated through every log line

Zero local case storage — pure async proxy to upstream Frappe API servers

Rust + Leptos frontend compiled to WebAssembly — fine-grained reactive CSR

Full case management: hearings, statuses, documents, role-based access

Outcome

Multiple financial institutions now run on the same codebase with cryptographic isolation. The B2C-to-B2B migration preserved the entire borrower-facing Resolution Desk while adding the full institutional layer on top. The observability layer gives full request tracing and real-time Prometheus dashboards in production.