B2B Financial Institution Portal
Multi-tenant ODR dashboard for banks and NBFCs
B2C → B2B migration
Architecture
0 case records
Local storage
Cryptographic per-tenant
Isolation
Prometheus + JSON logs
Observability
Project overview
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

Secure Tenant Portal Sign-in

OTP Generated

Arbitration & Litigation Portfolio Summary

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.
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 tenantEngineering 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.