Architecture Overview

What orvo-abha is, in one paragraph

orvo-abha is a standalone backend service whose entire job is to talk to India's national health data network, ABDM (Ayushman Bharat Digital Mission), on behalf of the Orvo product family. It is not a hospital management system, not a patient portal, and not where appointments/billing/EMR data normally lives — those live in main Orvo (the orvo-hub facility app, the orvo-web patient/doctor app, and their shared database). orvo-abha's only responsibility is everything that touches ABDM: creating and managing a patient's national health ID (ABHA), linking a patient's hospital visit to that ID, requesting/granting/serving consent to share medical records between hospitals, moving the actual encrypted medical records around, registering doctors in the national professional registry (HPR), and submitting insurance claims (NHCX). Think of it as a translator and record-keeper sitting between "how Orvo works" and "how India's government health network works" — the two speak different protocols, use different identifiers, and have different compliance rules, and this service is where that translation happens.

Why it's a separate service, not a module inside main Orvo

A few reasons, all load-bearing:

  1. ABDM's rules are not Orvo's rules. ABDM mandates specific data-retention behavior (e.g. hard-deleting records when a consent is revoked or expires — see the consent lifecycle doc), specific cryptography (Fidelius ECDH+AES-GCM for health data, RSA for HPR OTPs/passwords, detached-JWS artefact signatures), specific identifier formats (ABHA address, ABHA number, HIP/HIU IDs), and asynchronous callback-based APIs everywhere. None of that is how a normal internal CRUD API is shaped, and none of it should leak into how main Orvo models its own patients/facilities/staff.
  2. It's a distinct trust boundary. orvo-abha holds ABDM gateway credentials (client secrets, encrypted at rest with AES-256-GCM), signs/decrypts patient medical data, and receives unauthenticated inbound webhooks directly from ABDM's gateway (protected only by not being guessable + audit logging, not by a login). Isolating that blast radius from the rest of the product is deliberate.
  3. It can be versioned and evolve on ABDM's schedule, not Orvo's. ABDM's own API versions (v0.5, v3), sandbox quirks, and compliance requirements change independently of Orvo's product roadmap.
  4. One deployment currently backs many facilities under a single shared ABDM bridge/client id (see Facility Settings in the platform API doc) — a cross-cutting concern that doesn't map cleanly onto "one facility, one app instance."

Who the actors are (ABDM vocabulary)

If you're not familiar with ABDM's terminology, here's the minimum glossary to follow the rest of these docs:

Term Plain-English meaning
ABDM Ayushman Bharat Digital Mission — India's national digital health program.
ABHA Ayushman Bharat Health Account — a patient's national health ID. Comes in two forms: a 14-digit ABHA number, and a human-friendly ABHA address (like an email, e.g. ravi.kumar@sbx).
HIP (Health Information Provider) A system that holds a patient's medical records — typically a hospital/clinic. orvo-abha plays this role on behalf of every facility running on the Orvo platform.
HIU (Health Information User) A system that requests a patient's medical records from a HIP — could be another hospital, an insurer, or the patient's own app. orvo-abha plays this role too, e.g. when a facility wants to pull in a patient's history from elsewhere, or when the PHR app does a "self-fetch."
PHR (Personal Health Record app) The patient-facing app experience — an ABHA holder managing their own ID, consents, and record timeline. This is orvo-phr and the /v1/phr/* and /v2/phr/* routes.
HPR (Healthcare Professional Registry) India's national registry of doctors/nurses — separate identity system from ABHA, for professionals rather than patients.
CM (Consent Manager) ABDM's own component that hosts the patient-facing "approve/deny this consent request" screen. orvo-abha never renders this screen — ABDM does, on its own consent-manager app.
Consent artefact The signed document ABDM issues once a patient approves a consent request, spelling out exactly what data, what date range, and which care contexts were authorized.
Care context A single clinical encounter/episode (e.g. one OP visit, one lab report) that gets linked to a patient's ABHA so it can later be found/shared.
NHCX National Health Claims Exchange — ABDM's insurance-claims module (eligibility checks, pre-authorization, claims, payments).

The two roles this one codebase plays, simultaneously

Because orvo-abha serves many facilities, it is frequently acting as both HIP and HIU at the same time, for different requests:

This dual role is why the consent and health-information-transfer modules (see the API reference) each have two mirrored halves — an inbound side and an outbound side — rather than one linear flow. It's the single most important architectural fact to internalize before reading the API reference in depth.

High-level data flow

┌──────────────┐        ┌──────────────┐        ┌──────────────┐
│   orvo-hub    │        │              │        │   ABDM       │
│  (facility    │◄──────►│              │◄──────►│   Gateway     │
│   operator)   │        │              │        │  (HIE-CM)     │
└──────────────┘        │              │        └───────┬──────┘
                         │  orvo-abha   │                │
┌──────────────┐        │  (this repo) │                │ async
│   orvo-phr    │◄──────►│              │                │ callbacks
│  (patient)    │        │              │                ▼
└──────────────┘        │              │        ┌──────────────┐
                         │              │◄──────►│ Other HIPs/   │
┌──────────────┐        │              │        │ HIUs/Insurers │
│   orvo-web    │◄──────►│              │        │  (via ABDM)   │
│  (doctor)     │        └──────┬───────┘        └──────────────┘
└──────────────┘                │
                                 ▼
                         ┌──────────────┐
                         │  PostgreSQL   │
                         │ (orvo-abha's  │
                         │  own schema)  │
                         └──────────────┘

Every "outbound" call from an Orvo frontend (orvo-hub, orvo-phr, orvo-web) into orvo-abha triggers a synchronous HTTP response (usually a 202 Accepted acknowledgement) plus, later, one or more asynchronous inbound callbacks from the ABDM gateway that carry the actual result. Nothing in this system works as a simple request/response round trip once ABDM is involved — every integration point (orvo-hub, orvo-web, orvo-phr, and any future main-Orvo integration) has to be built around that asynchrony, typically by listening for the Pusher realtime events documented in the Platform API doc, or by polling a status endpoint as a fallback.

A concrete walkthrough: HIP-initiated linking, end to end

The paragraph above is abstract until you trace one real flow through it. Here's the "facility already knows the patient's ABHA and wants to proactively push a care-context link" flow (see the HIP Identity & Linking doc for the full request/response shapes) — chosen because it touches both halves of the dual-role pattern (this deployment is HIP the whole way through, but the CM/ABDM side is a genuinely separate party) and shows why nothing here can be a single request/response round trip:

orvo-hub                     orvo-abha                          ABDM Gateway / CM app
   │                             │                                        │
   │  POST /v1/link/token/       │                                        │
   │  generate                   │                                        │
   ├────────────────────────────►│  builds+submits link-token init  ─────►│
   │  ◄── 202 Accepted ──────────┤                                        │
   │                             │                                        │  (ABDM notifies patient's
   │                             │  ◄── inbound callback: ────────────────┤   ABHA app; they approve)
   │                             │      link-token issued                 │
   │  ◄── Pusher: ───────────────┤  (Consent/LinkingToken row updated,    │
   │      "link-token:received"  │   patient's linking token stored)      │
   │                             │                                        │
   │  POST /v1/hip-linking/      │                                        │
   │  patient/links/care-context │                                        │
   ├────────────────────────────►│  submits on_carecontext confirm  ─────►│
   │  ◄── 202 Accepted ──────────┤                                        │
   │                             │  ◄── inbound callback: ────────────────┤
   │                             │      on_carecontext result             │
   │  ◄── Pusher: ───────────────┤  (CareContext.linked flips true/false) │
   │      care-context linked    │                                        │

Every rightward arrow into ABDM gets an immediate 202 and nothing else — the actual outcome (a token being issued, a link succeeding or being rejected) only exists once the corresponding leftward inbound callback lands, on a completely separate HTTP request that ABDM initiates on its own schedule. orvo-hub never blocks on that outcome; it either subscribes to the Pusher channel from the Integration Contract's §4, or polls the local CareContext/LinkingToken row. This is also exactly why idempotency matters everywhere in this system (see below) — ABDM redelivering either callback must not be allowed to re-run the local state transition twice.

Reliability & operational patterns

A handful of patterns recur across every ABDM-facing module, because the same problem (an unreliable, asynchronous, multi-tenant third-party protocol) shows up everywhere: dual in-memory/DB-backed session caching for ABDM tokens (one manager for the single-credential M1/identity family, one per-facility for the BridgeConfig-backed families, both with a failure cooldown so an ABDM outage doesn't turn into a hammering retry loop); a per-facility gateway client that injects the required ABDM headers, retries transient 5xx/429 but never 4xx, and logs every call to GatewayTransaction; a shared error normalizer (abdm-errors.ts) that maps ABDM's error phrases to HTTP statuses and a retryable flag; idempotency guards on every inbound callback handler (ABDM redelivers callbacks — this is observed behavior, not a hypothetical, and a missing guard here previously produced a production incident with 1,297 duplicate Consent rows for one patient); and a deliberate fire-and-forget pattern for a couple of specific, latency-sensitive writes (patient profile/token persistence right after login) that is not used for anything where a silently-lost failure would be worse than a slightly slower response. The full detail on each of these, with the specific files involved, is in the Operations Guide's Runtime Internals section — this paragraph is here so you know the pattern exists before you go looking for it.

Module map

The codebase (src/modules/abdm/) is split by which ABDM actor a module serves, not by feature:

Platform modules that aren't ABDM-specific live outside abdm/: src/modules/realtime/ (Pusher channel resolution), src/modules/log/ (operational log/diagnostics), src/modules/facility-notifications/ (operator notification bell).

Data model, at a glance

Every table lives in orvo-abha's own PostgreSQL database (Prisma-managed), entirely separate from main Orvo's database. The full list of models (see prisma/schema.prisma) groups into a few families:

Nothing here mirrors main Orvo's own Patient/Encounter/Staff tables — the only cross-system linkage is the ABHA address / ABHA number itself, and the internal facilityId UUID that a given hipId resolves to. See the Integration Contract for exactly how that linkage is meant to work.

Security model summary