ABDM Integration
Purpose: ABDM (Ayushman Bharat Digital Mission) integration for Indian healthcare digital health records, ported from legacy
parts/abdmto the new domain-driven architecture underdomains/integration/. Schema:integration(PostgreSQL) Tenant Scoped: Yes (all tables exceptabdm_hip_registry) RLS Enabled: Yes Primary Keys: UUIDv7 Timestamps: TIMESTAMPTZ
Table of Contents
- Overview
- Architecture
- Database Schema
- API Endpoints
- Webhook Flow
- State Machines
- Security
- Frontend Integration
- Background Jobs
- Migration from Legacy
Overview
What is ABDM
ABDM (Ayushman Bharat Digital Mission) is India's national digital health infrastructure. It enables standardized health data exchange between healthcare providers, patients, and health information users. The core concepts are:
- ABHA (Ayushman Bharat Health Account): A unique health ID for every Indian citizen, created via Aadhaar or mobile OTP verification.
- HIP (Health Information Provider): A facility that holds patient health records (e.g., a clinic with prescriptions and encounter data).
- HIU (Health Information User): A facility that requests health records from other facilities, subject to patient consent.
- PHR (Personal Health Record): A patient-facing app where patients manage their health data, approve consent requests, and initiate discovery of their records.
- Consent Manager (CM): The ABDM layer that mediates consent between patients, HIPs, and HIUs.
Why We Integrate
Kulcare facilities operating in India need to:
- Register as HIP/HIU with the ABDM network
- Create ABHA accounts for patients during clinic visits
- Link clinical records (prescriptions, encounters, diagnostic reports) to patient ABHA identities
- Respond to patient-initiated discovery requests from the PHR app
- Manage consent lifecycle for health data sharing between facilities
- Exchange encrypted FHIR R4 bundles for health data transfer
All of this targets the ABDM Gateway V3 API.
Integration Phases
The integration is implemented in four phases:
| Phase | Scope | Models |
|---|---|---|
| Phase 1: Infrastructure | Facility registration, gateway tokens, API logging, webhook callbacks | AbdmFacilityRegistration, AbdmHipRegistry, AbdmGatewayToken, AbdmApiLog, AbdmCallback |
| Phase 2: ABHA Registration | Patient ABHA creation, PHR login, profile management | AbdmPatientProfile, AbdmAuthSession |
| Phase 3: HIP Linking | HIP-initiated linking, patient-initiated discovery, care context management | AbdmLinkRequest, AbdmDiscoveryRequest, AbdmCareContext |
| Phase 4: Consent & Data | Consent lifecycle, artefacts, encrypted data transfer, audit logging | AbdmConsent, AbdmConsentArtefact, AbdmDataTransfer, AbdmConsentAuditLog |
Architecture
Domain Structure
ABDM Gateway V3
|
| Outbound API calls (token, link, consent, data)
v
+----------------------------------------------+
| Integration::Abdm::ApiClient |
| - Lazy token refresh via GatewayTokenService |
| - Retry with exponential backoff (3 retries) |
| - Circuit breaker (5 failures / 60s timeout) |
| - Automatic logging to AbdmApiLog |
+----------------------------------------------+
|
v
+----------------------------------------------+
| domains/integration/ |
| +-- models/ 14 ActiveRecord models |
| +-- controllers/ REST + webhook endpoints|
| +-- services/ Business logic |
| +-- jobs/ Sidekiq async workers |
| +-- blueprints/ Blueprinter serializers |
| +-- policies/ Pundit authorization |
| +-- routes.rb Route definitions |
+----------------------------------------------+
^
| Inbound webhooks (JWT-verified)
|
ABDM Gateway CallbacksFile Locations
| Layer | Location | Responsibility |
|---|---|---|
| Models | domains/integration/models/ | Data persistence, validations, state machines |
| Controllers | domains/integration/controllers/abdm/ | REST API and webhook endpoints |
| Services | domains/integration/services/abdm/ | Business logic, API calls, encryption |
| Jobs | domains/integration/jobs/abdm/ | Async processing (callbacks, data transfer, erasure) |
| Blueprints | domains/integration/blueprints/ | JSON serialization (normal/detailed views) |
| Policies | domains/integration/policies/ | Authorization via Pundit |
Key Patterns
BaseRecord: All integration models inherit from Integration::BaseRecord which sets the schema prefix:
class Integration::BaseRecord < ApplicationRecord
self.abstract_class = true
self.table_name_prefix = 'integration.'
endTenantScoped: All tenant-aware models include Shared::Concerns::TenantScoped for automatic tenant filtering.
Result Pattern: All services return a consistent Result struct:
Result = Struct.new(:success?, :data, :errors, keyword_init: true)Blueprinter Serialization: Each model has a blueprint with normal and detailed views for API responses.
Key Services
| Service | File | Responsibility |
|---|---|---|
GatewayTokenService | services/abdm/gateway_token_service.rb | Per-tenant JWT token management with lazy refresh |
ApiClient | services/abdm/api_client.rb | HTTP client with retry, circuit breaker, logging |
CallbackProcessor | services/abdm/callback_processor.rb | Idempotent webhook routing and processing |
WebhookAuthenticator | services/abdm/webhook_authenticator.rb | ABDM JWT signature verification with JWKS |
FideliusService | services/abdm/encryption/fidelius_service.rb | ECDH curve25519 encryption via sidecar |
RsaEncryptionService | services/abdm/encryption/rsa_encryption_service.rb | RSA encryption for Aadhaar data |
RegistrationService | services/abdm/abha/registration_service.rb | ABHA registration (Aadhaar/mobile OTP) |
ProfileService | services/abdm/abha/profile_service.rb | ABHA profile fetch, QR code, card |
LoginService | services/abdm/phr/login_service.rb | PHR login and token management |
LinkService | services/abdm/hip/link_service.rb | HIP-initiated linking workflow |
PatientMatcher | services/abdm/hip/patient_matcher.rb | Patient discovery matching logic |
DiscoveryService | services/abdm/hip/discovery_service.rb | Patient-initiated discovery handling |
DataTransferService | services/abdm/hip/data_transfer_service.rb | HIP data encryption and push |
ConsentRequestService | services/abdm/hiu/consent_request_service.rb | HIU consent request creation |
DataFetchService | services/abdm/hiu/data_fetch_service.rb | HIU data request and decryption |
Cm::ConsentService | services/abdm/cm/consent_service.rb | Patient consent approve/deny/revoke |
Cm::ArtefactService | services/abdm/cm/artefact_service.rb | Consent artefact management |
BundleBuilder | services/abdm/fhir/bundle_builder.rb | FHIR R4 document bundle construction |
MedicationRequestBundle | services/abdm/fhir/medication_request_bundle.rb | Prescription FHIR bundles |
EncounterBundle | services/abdm/fhir/encounter_bundle.rb | Encounter FHIR bundles |
Webhook Handlers
| Handler | File | Callbacks Handled |
|---|---|---|
HipLinkHandler | services/abdm/webhooks/hip_link_handler.rb | on_token_generate, on_add_contexts, auth_notify, profile_share |
HipDiscoveryHandler | services/abdm/webhooks/hip_discovery_handler.rb | discover, link_init, link_confirm |
HipConsentHandler | services/abdm/webhooks/hip_consent_handler.rb | consent_notify (HIP side) |
HipDataHandler | services/abdm/webhooks/hip_data_handler.rb | on_request (data request from HIU) |
HiuConsentHandler | services/abdm/webhooks/hiu_consent_handler.rb | consent_notify, on_fetch (HIU side) |
HiuDataHandler | services/abdm/webhooks/hiu_data_handler.rb | data_notification (data push from HIP) |
Database Schema
All tables reside in the integration PostgreSQL schema. All tables use UUIDv7 primary keys and TIMESTAMPTZ for timestamps.
Phase 1: Infrastructure Tables
integration.abdm_gateway_tokens
Per-tenant JWT tokens for ABDM Gateway V3 API authentication. Tokens are refreshed lazily by ApiClient when expired or within 5 minutes of expiry.
| Column | Type | Required | Description |
|---|---|---|---|
| id | UUID | Yes | UUIDv7 primary key |
| tenant_id | UUID | Yes | Tenant isolation |
| provider | VARCHAR(20) | Yes | Provider identifier (default: abdm_v3) |
| access_token | TEXT | Yes | JWT access token |
| expires_at | TIMESTAMPTZ | Yes | Token expiration |
| refresh_token | TEXT | No | Refresh token |
| refresh_expires_at | TIMESTAMPTZ | No | Refresh token expiration |
Key methods: self.current(provider:), expired?, near_expiry?, needs_refresh?
integration.abdm_facility_registrations
HIP/HIU service registrations per facility. A facility can have separate HIP and HIU registrations. Changes are automatically synced to abdm_hip_registry via a database trigger.
| Column | Type | Required | Description |
|---|---|---|---|
| id | UUID | Yes | UUIDv7 primary key |
| tenant_id | UUID | Yes | Tenant isolation |
| facility_id | UUID | Yes | FK to identity.facilities |
| registration_type | VARCHAR | Yes | HIP or HIU |
| service_id | VARCHAR(100) | Yes | ABDM-assigned service ID (globally unique) |
| service_name | VARCHAR(255) | Yes | Registered service name |
| callback_url | VARCHAR(500) | No | Webhook callback base URL |
| active | BOOLEAN | Yes | Registration status |
Key methods: self.hip_for_facility(id), self.hiu_for_facility(id)
integration.abdm_hip_registry
Non-RLS lookup table for resolving webhook X-HIP-ID / X-HIU-ID headers to tenant and facility context. This table does NOT use TenantScoped because it must be queryable before RLS context is established during webhook pre-authentication. Automatically synced from abdm_facility_registrations via a PostgreSQL trigger.
| Column | Type | Required | Description |
|---|---|---|---|
| service_id | VARCHAR(100) | Yes | Primary key (ABDM service ID) |
| tenant_id | UUID | Yes | FK to identity.tenants |
| facility_id | UUID | Yes | FK to identity.facilities |
| registration_type | VARCHAR | Yes | HIP or HIU |
| active | BOOLEAN | Yes | Whether entry is active |
Key method: self.resolve(service_id) -- used by WebhookBaseController to establish RLS context.
integration.abdm_api_logs
Audit log of all outbound ABDM API requests. Created automatically by ApiClient for every API call. Partitioned by month for high-volume deployments.
| Column | Type | Required | Description |
|---|---|---|---|
| id | UUID | Yes | UUIDv7 primary key |
| tenant_id | UUID | Yes | Tenant isolation |
| request_id | VARCHAR(100) | Yes | ABDM request ID |
| url | VARCHAR(500) | Yes | API endpoint URL |
| http_method | VARCHAR(10) | Yes | HTTP method |
| request_headers | JSONB | No | Sanitized request headers |
| request_payload | JSONB | No | Request body |
| response_status | INTEGER | No | HTTP response status code |
| response_body | JSONB | No | Response body |
| success | BOOLEAN | No | Whether request succeeded |
| duration_ms | INTEGER | No | Response time in milliseconds |
| retry_count | INTEGER | No | Number of retry attempts |
| error_message | TEXT | No | Error description |
integration.abdm_callbacks
Inbound webhook payload audit log with idempotent processing pipeline.
| Column | Type | Required | Description |
|---|---|---|---|
| id | UUID | Yes | UUIDv7 primary key |
| tenant_id | UUID | Yes | Tenant isolation |
| facility_id | UUID | Yes | FK to identity.facilities |
| callback_type | VARCHAR(100) | Yes | Handler routing key |
| request_id | VARCHAR | No | ABDM request ID |
| transaction_id | VARCHAR | No | ABDM transaction ID |
| payload | JSONB | Yes | Full webhook payload |
| status | VARCHAR | Yes | received, processing, processed, failed, dead_letter |
| idempotency_key | VARCHAR(250) | No | Dedup key: {callback_type}:{request_id} |
| processing_attempts | INTEGER | No | Number of processing attempts |
| max_attempts | INTEGER | No | Maximum retry attempts |
| next_retry_at | TIMESTAMPTZ | No | Next retry time (exponential backoff) |
| error_code | VARCHAR | No | ABDM error code |
| error_message | TEXT | No | Error description |
| processed_at | TIMESTAMPTZ | No | When processing completed |
Key methods: claim! (atomic claim for processing), mark_processed!, mark_failed!(error_code:, error_message:). Retry uses exponential backoff: 30s, 60s, 120s, etc.
Phase 2: ABHA Registration Tables
integration.abdm_patient_profiles
ABHA identity linked to a patient. A patient has one ABHA identity regardless of facility. Unique constraint on (tenant_id, patient_id). The profile_data JSONB column contains PII (name, DOB, gender, address) and is encrypted at the application level using encrypts :profile_data.
| Column | Type | Required | Description |
|---|---|---|---|
| id | UUID | Yes | UUIDv7 primary key |
| tenant_id | UUID | Yes | Tenant isolation |
| patient_id | UUID | Yes | FK to clinical.patients (unique per tenant) |
| abha_number | VARCHAR(14) | No | 14-digit ABHA number (e.g., 91-1234-5678-9012) |
| abha_address | VARCHAR(100) | No | ABHA address (e.g., user@abdm) |
| health_id | VARCHAR(100) | No | Legacy health ID |
| registration_status | VARCHAR | Yes | See state machine section |
| profile_data | JSONB | No | Encrypted PII (name, DOB, gender, address) |
| phr_linked | BOOLEAN | No | Whether PHR app is linked |
Key methods: self.find_by_abha(number), self.find_by_address(address), registered?, active?
integration.abdm_auth_sessions
Multi-step authentication and registration flow state machine with TTL (default 15 minutes). The step_data JSONB column may contain PII and is encrypted at the application level using encrypts :step_data.
| Column | Type | Required | Description |
|---|---|---|---|
| id | UUID | Yes | UUIDv7 primary key |
| tenant_id | UUID | Yes | Tenant isolation |
| facility_id | UUID | Yes | FK to identity.facilities |
| patient_id | UUID | No | FK to clinical.patients (set after match) |
| initiated_by_id | UUID | No | FK to identity.users |
| session_type | VARCHAR | Yes | See session types below |
| status | VARCHAR | Yes | Current state |
| txn_id | VARCHAR(100) | No | ABDM transaction ID |
| step_data | JSONB | No | Encrypted step-specific data |
| expires_at | TIMESTAMPTZ | Yes | Session TTL |
| completed_at | TIMESTAMPTZ | No | When session completed |
Session Types: registration_aadhaar, registration_mobile, registration_enrollment, login_mobile, login_email, login_abha_address, login_abha_number, login_aadhaar, hip_auth
Key methods: transition_to!(status, **attrs), expire_if_needed!, self.find_active_by_txn(txn_id)
Phase 3: HIP Linking Tables
integration.abdm_link_requests
HIP-initiated linking flow. Consolidates legacy hip_link_requests + hip_link_token_requests since V3 uses a unified flow.
| Column | Type | Required | Description |
|---|---|---|---|
| id | UUID | Yes | UUIDv7 primary key |
| tenant_id | UUID | Yes | Tenant isolation |
| facility_id | UUID | Yes | FK to identity.facilities |
| patient_id | UUID | Yes | FK to clinical.patients |
| abdm_patient_profile_id | UUID | No | FK to abdm_patient_profiles |
| status | VARCHAR | Yes | Current state (see state machine) |
| abha_number | VARCHAR(14) | No | Patient's ABHA number |
| abha_address | VARCHAR(100) | No | Patient's ABHA address |
| auth_mode | VARCHAR(20) | No | Authentication mode used |
| link_token | TEXT | No | ABDM-issued link token |
| link_token_expires_at | TIMESTAMPTZ | No | Token expiration |
| abdm_api_log_id | UUID | No | FK to last API call log |
| abdm_callback_id | UUID | No | FK to last callback |
Associations: belongs_to :facility, belongs_to :patient, has_many :care_contexts
integration.abdm_discovery_requests
Patient-initiated discovery and linking. Created when ABDM sends a discover callback. The patient_demographics JSONB contains name, gender, yearOfBirth, and verified identifiers from the webhook payload.
| Column | Type | Required | Description |
|---|---|---|---|
| id | UUID | Yes | UUIDv7 primary key |
| tenant_id | UUID | Yes | Tenant isolation |
| facility_id | UUID | Yes | FK to identity.facilities |
| patient_id | UUID | No | FK to clinical.patients (set after match) |
| transaction_id | VARCHAR(100) | Yes | ABDM transaction ID |
| abha_address | VARCHAR(100) | No | Patient's ABHA address |
| patient_demographics | JSONB | Yes | Name, gender, yearOfBirth, identifiers |
| verification_code | VARCHAR(10) | No | 6-digit code for link confirmation |
| status | VARCHAR | Yes | Current state |
| abdm_callback_id | UUID | No | FK to originating callback |
Key method: generate_verification_code! -- generates random 6-digit code for link confirmation.
integration.abdm_care_contexts
Linked care contexts with FHIR bundles. Core record linking clinical data to ABDM.
The care_context_reference is environment-aware to prevent cross-environment collisions:
- Production:
CC-PR-{id} - Staging:
CC-PR-STG-{id} - Development:
CC-PR-DEV-{id}
| Column | Type | Required | Description |
|---|---|---|---|
| id | UUID | Yes | UUIDv7 primary key |
| tenant_id | UUID | Yes | Tenant isolation |
| facility_id | UUID | Yes | FK to identity.facilities |
| patient_id | UUID | Yes | FK to clinical.patients |
| abdm_patient_profile_id | UUID | No | FK to abdm_patient_profiles |
| link_request_id | UUID | No | FK to abdm_link_requests (HIP-initiated) |
| discovery_request_id | UUID | No | FK to abdm_discovery_requests (patient-initiated) |
| care_context_reference | VARCHAR(50) | Yes | Unique environment-aware reference |
| care_context_display | VARCHAR(255) | Yes | Human-readable name (e.g., CAREPLAN-07-January-2025) |
| resource_type | VARCHAR | Yes | Prescription, Encounter, DiagnosticReport |
| resource_id | UUID | Yes | ID of the clinical resource |
| resource_schema | VARCHAR(20) | Yes | Schema where resource lives (e.g., clinical) |
| sync_status | VARCHAR | Yes | See state machine section |
| fhir_bundle | JSONB | No | FHIR R4 document bundle for data transfer |
Key methods: self.generate_reference(type, id), self.generate_display(type, created_at)
Phase 4: Consent and Data Exchange Tables
integration.abdm_consents
Unified consent lifecycle for both HIP and HIU directions. Status transitions are automatically logged to AbdmConsentAuditLog.
| Column | Type | Required | Description |
|---|---|---|---|
| id | UUID | Yes | UUIDv7 primary key |
| tenant_id | UUID | Yes | Tenant isolation |
| facility_id | UUID | Yes | FK to identity.facilities |
| patient_id | UUID | Yes | FK to clinical.patients |
| abdm_patient_profile_id | UUID | No | FK to abdm_patient_profiles |
| direction | VARCHAR | Yes | hip or hiu |
| status | VARCHAR | Yes | See state machine section |
| purpose | VARCHAR | Yes | care_management, break_the_glass, public_health, healthcare_payment, disease_research, patient_requested |
| hi_types | TEXT[] | Yes | Health information types (e.g., OPConsultation, Prescription) |
| consent_id | VARCHAR(100) | No | ABDM consent ID |
| requester_id | VARCHAR(100) | No | Requesting HIP/HIU service ID |
| date_range_from | TIMESTAMPTZ | No | Data access period start |
| date_range_to | TIMESTAMPTZ | No | Data access period end |
| consent_expiry | TIMESTAMPTZ | No | When consent expires |
| data_erase_at | TIMESTAMPTZ | No | When data must be erased |
| granted_at | TIMESTAMPTZ | No | When consent was granted |
| denied_at | TIMESTAMPTZ | No | When consent was denied |
| revoked_at | TIMESTAMPTZ | No | When consent was revoked |
| requested_by_id | UUID | No | FK to identity.users |
| consent_artifact | JSONB | No | Raw consent artifact (erased on data_erase_at) |
| abdm_callback_id | UUID | No | FK to originating callback |
Associations: has_many :consent_artefacts, has_many :data_transfers, has_many :audit_logs
integration.abdm_consent_artefacts
Detailed consent artefacts received from ABDM (primarily HIU side). The artefact_id is globally unique per the ABDM specification. The fhir_records JSONB is erased when data_erase_at is reached.
| Column | Type | Required | Description |
|---|---|---|---|
| id | UUID | Yes | UUIDv7 primary key |
| tenant_id | UUID | Yes | Tenant isolation |
| abdm_consent_id | UUID | Yes | FK to abdm_consents |
| artefact_id | VARCHAR(100) | Yes | ABDM artefact ID (globally unique) |
| fetch_status | VARCHAR | Yes | pending, fetching, fetched, failed, erased |
| care_contexts | JSONB | No | Care context references |
| fhir_records | JSONB | No | Decrypted FHIR records (erased on compliance) |
| data_erase_at | TIMESTAMPTZ | No | When records must be erased |
| fetched_at | TIMESTAMPTZ | No | When FHIR records were fetched |
| erased_at | TIMESTAMPTZ | No | When data was erased |
Key method: erase_data! -- nulls out fhir_records, care_contexts, sets fetch_status='erased'
integration.abdm_data_transfers
Tracks encrypted data transfers in both directions. The key_material JSONB stores ECDH key pairs for Fidelius encryption/decryption.
| Column | Type | Required | Description |
|---|---|---|---|
| id | UUID | Yes | UUIDv7 primary key |
| tenant_id | UUID | Yes | Tenant isolation |
| facility_id | UUID | Yes | FK to identity.facilities |
| abdm_consent_id | UUID | Yes | FK to abdm_consents |
| consent_artefact_id | UUID | No | FK to abdm_consent_artefacts |
| direction | VARCHAR | Yes | hip or hiu |
| status | VARCHAR | Yes | See state machine section |
| transaction_id | VARCHAR(100) | No | ABDM transaction ID |
| data_push_url | VARCHAR(500) | No | URL for HIP to push data |
| key_material | JSONB | No | ECDH key pairs (sender/receiver keys, nonces) |
| total_contexts | INTEGER | No | Total care contexts to transfer |
| transferred_contexts | INTEGER | No | Care contexts successfully transferred |
| error_code | VARCHAR(50) | No | Error code |
| error_message | TEXT | No | Error description |
| started_at | TIMESTAMPTZ | No | When encryption/transfer started |
| completed_at | TIMESTAMPTZ | No | When transfer completed/failed |
integration.abdm_consent_audit_logs
Immutable append-only audit trail for consent status transitions. Required for ABDM regulatory compliance. No UPDATE or DELETE operations are permitted (enforced by PostgreSQL RULEs and the readonly? method on persisted records).
| Column | Type | Required | Description |
|---|---|---|---|
| id | UUID | Yes | UUIDv7 primary key |
| tenant_id | UUID | Yes | Tenant isolation |
| abdm_consent_id | UUID | Yes | FK to abdm_consents |
| previous_status | VARCHAR | No | Status before transition |
| new_status | VARCHAR | Yes | Status after transition |
| triggered_by | VARCHAR | Yes | abdm_callback, user_action, system_expiry |
| triggered_by_id | VARCHAR | No | User ID or callback ID |
| change_reason | TEXT | No | Human-readable reason |
| created_at | TIMESTAMPTZ | Yes | When transition occurred |
ERD
API Endpoints
File: domains/integration/routes.rb
All routes are nested under /integration/abdm. Authenticated endpoints require a valid user session and tenant context. Webhook endpoints use ABDM JWT authentication instead.
Phase 1: Facility Registration
| Method | Path | Controller | Description |
|---|---|---|---|
| GET | /integration/abdm/facility_registrations | FacilityRegistrationsController#index | List facility registrations |
| POST | /integration/abdm/facility_registrations | FacilityRegistrationsController#create | Register facility as HIP/HIU |
| GET | /integration/abdm/facility_registrations/:id | FacilityRegistrationsController#show | Get registration details |
| PATCH | /integration/abdm/facility_registrations/:id | FacilityRegistrationsController#update | Update registration |
Phase 2: ABHA Registration and PHR Login
ABHA Registration (Aadhaar OTP flow):
| Method | Path | Description |
|---|---|---|
| POST | /integration/abdm/abha/registrations/send_otp | Send Aadhaar or mobile OTP |
| POST | /integration/abdm/abha/registrations/verify_otp | Verify OTP and progress session |
| POST | /integration/abdm/abha/registrations/suggest_address | Get suggested ABHA addresses |
| POST | /integration/abdm/abha/registrations/create_address | Create ABHA address and finalize |
ABHA Profile Management:
| Method | Path | Description |
|---|---|---|
| GET | /integration/abdm/abha/profiles/:id | Get patient's ABHA profile |
| POST | /integration/abdm/abha/profiles/:id/fetch_profile | Refresh profile from ABDM |
| GET | /integration/abdm/abha/profiles/:id/qr_code | Get ABHA QR code |
| GET | /integration/abdm/abha/profiles/:id/card | Get ABHA card image |
PHR Session Management:
| Method | Path | Description |
|---|---|---|
| POST | /integration/abdm/phr/sessions/send_otp | Send PHR login OTP |
| POST | /integration/abdm/phr/sessions/verify_otp | Verify PHR login OTP |
| POST | /integration/abdm/phr/sessions/verify_user | Verify user identity |
| POST | /integration/abdm/phr/sessions/refresh_token | Refresh PHR session token |
Phase 3: HIP Linking and Care Contexts
| Method | Path | Description |
|---|---|---|
| GET | /integration/abdm/link_requests | List link requests |
| GET | /integration/abdm/link_requests/:id | Get link request details |
| POST | /integration/abdm/link_requests/fetch_modes | Fetch available auth modes for patient |
| POST | /integration/abdm/link_requests/auth_init | Initiate auth for linking |
| POST | /integration/abdm/link_requests/auth_confirm | Confirm auth with OTP/token |
| POST | /integration/abdm/link_requests/add_contexts | Add care contexts to link |
| GET | /integration/abdm/care_contexts | List care contexts |
| GET | /integration/abdm/care_contexts/:id | Get care context details |
Phase 4: Consent Management and Data Exchange
Consent (HIP/HIU):
| Method | Path | Description |
|---|---|---|
| GET | /integration/abdm/consents | List consents |
| POST | /integration/abdm/consents | Create HIU consent request |
| GET | /integration/abdm/consents/:id | Get consent details |
| GET | /integration/abdm/health_records | List health records |
| GET | /integration/abdm/health_records/:id | Get health record details |
Consent Management (patient-facing CM operations):
| Method | Path | Description |
|---|---|---|
| POST | /integration/abdm/cm/consents/:id/approve | Approve a consent request |
| POST | /integration/abdm/cm/consents/:id/deny | Deny a consent request |
| POST | /integration/abdm/cm/consents/:id/revoke | Revoke a granted consent |
| GET | /integration/abdm/cm/artefacts | List consent artefacts |
| GET | /integration/abdm/cm/artefacts/:id | Get artefact details |
Webhooks (ABDM JWT Auth, No User Session)
| Method | Path | Controller | Description |
|---|---|---|---|
| GET | /integration/abdm/webhooks/heartbeat | HeartbeatController#show | Health check |
| POST | /integration/abdm/webhooks/hip/on_token_generate | HipLinkWebhooksController | Link token generated callback |
| POST | /integration/abdm/webhooks/hip/on_add_contexts | HipLinkWebhooksController | Contexts added callback |
| POST | /integration/abdm/webhooks/hip/auth_notify | HipLinkWebhooksController | Auth notification callback |
| POST | /integration/abdm/webhooks/hip/profile_share | HipLinkWebhooksController | Profile share callback |
| POST | /integration/abdm/webhooks/hip/consent_notify | HipConsentWebhooksController | HIP consent notification |
| POST | /integration/abdm/webhooks/hip/on_request | HipDataWebhooksController | Data request from HIU |
| POST | /integration/abdm/webhooks/discovery/discover | HipDiscoveryWebhooksController | Patient discovery |
| POST | /integration/abdm/webhooks/discovery/link_init | HipDiscoveryWebhooksController | Patient link init |
| POST | /integration/abdm/webhooks/discovery/link_confirm | HipDiscoveryWebhooksController | Patient link confirm |
| POST | /integration/abdm/webhooks/hiu/consent_notify | HiuConsentWebhooksController | HIU consent notification |
| POST | /integration/abdm/webhooks/hiu/on_fetch | HiuConsentWebhooksController | HIU artefact fetch |
| POST | /integration/abdm/webhooks/hiu/data_notification | HiuDataWebhooksController | Data pushed by HIP |
Webhook Flow
Request Processing Pipeline
All ABDM webhooks follow the same pipeline, implemented in WebhookBaseController:
ABDM Gateway
|
| POST /integration/abdm/webhooks/{scope}/{action}
| Headers: Authorization: Bearer <JWT>, X-HIP-ID or X-HIU-ID
v
+----------------------------------------------------+
| 1. verify_webhook_signature |
| - Extract Bearer token from Authorization header |
| - Verify JWT signature against ABDM JWKS endpoint|
| - Check replay protection (5-minute window) |
| - On failure: 401 Unauthorized |
+----------------------------------------------------+
|
v
+----------------------------------------------------+
| 2. resolve_tenant_from_service_id |
| - Read X-HIP-ID or X-HIU-ID from headers |
| - Query AbdmHipRegistry.resolve(service_id) |
| - On not found: 404 Unknown service ID |
| - Extracts: tenant_id, facility_id |
+----------------------------------------------------+
|
v
+----------------------------------------------------+
| 3. set_webhook_rls_context |
| - SET app.current_tenant_id = tenant_id |
| - Set Shared::Current.tenant for app scoping |
+----------------------------------------------------+
|
v
+----------------------------------------------------+
| 4. log_and_enqueue_callback |
| - Create AbdmCallback record with: |
| callback_type, request_id, transaction_id, |
| payload, idempotency_key, status='received' |
| - Handle duplicates via RecordNotUnique |
| - Enqueue ProcessCallbackJob |
+----------------------------------------------------+
|
v
+----------------------------------------------------+
| 5. Return 202 Accepted |
+----------------------------------------------------+Callback Processing (async in Sidekiq)
ProcessCallbackJob
|
v
+----------------------------------------------------+
| CallbackProcessor.new(callback).process! |
| |
| 1. callback.claim! |
| - Atomic UPDATE WHERE status='received' |
| - Returns false if another worker claimed it |
| |
| 2. resolve_handler |
| - Lookup callback_type in HANDLER_REGISTRY |
| - Returns [HandlerClass, :method_name] |
| |
| 3. Execute handler.method(callback) |
| - Handler processes business logic |
| - Returns Result(success?, errors) |
| |
| 4. Update status |
| - Success: callback.mark_processed! |
| - Failure: callback.mark_failed!(...) |
| - After max_attempts: status -> 'dead_letter' |
+----------------------------------------------------+Handler Registry
| Callback Type | Handler Class | Method |
|---|---|---|
hip.on_token_generate | Webhooks::HipLinkHandler | on_token_generate |
hip.on_add_contexts | Webhooks::HipLinkHandler | on_add_contexts |
hip.auth_notify | Webhooks::HipLinkHandler | auth_notify |
hip.profile_share | Webhooks::HipLinkHandler | profile_share |
discovery.discover | Webhooks::HipDiscoveryHandler | discover |
discovery.link_init | Webhooks::HipDiscoveryHandler | link_init |
discovery.link_confirm | Webhooks::HipDiscoveryHandler | link_confirm |
hip.consent_notify | Webhooks::HipConsentHandler | consent_notify |
hip.data_request | Webhooks::HipDataHandler | on_request |
hiu.consent_notify | Webhooks::HiuConsentHandler | consent_notify |
hiu.on_fetch | Webhooks::HiuConsentHandler | on_fetch |
hiu.data_notification | Webhooks::HiuDataHandler | data_notification |
Idempotency
Duplicate webhook deliveries are handled at two levels:
- Database level: The
idempotency_keycolumn ({callback_type}:{request_id}) has a uniqueness constraint. Duplicate inserts raiseActiveRecord::RecordNotUnique, whichWebhookBaseControllercatches and returns the existing callback. - Processing level:
claim!uses an atomicUPDATE WHERE status='received'so only one worker can process a callback even if multipleProcessCallbackJobinstances are enqueued.
State Machines
All state machines use validated transitions via transition_to! methods. Invalid transitions add an error to the model and return false.
AbdmAuthSession
initiated --> otp_sent --> otp_verified --> address_suggested --> completed
\-> completed (no address step)
Any state --> expired (TTL, default 15 minutes)
Any state --> failed (error)| From | Allowed Transitions |
|---|---|
initiated | otp_sent, expired, failed |
otp_sent | otp_verified, expired, failed |
otp_verified | address_suggested, completed, expired, failed |
address_suggested | completed, expired, failed |
AbdmPatientProfile (registration_status)
pending --> aadhaar_verified --> mobile_verified --> registered --> active
|
v
deactivatedNote: This uses simple status updates rather than a formal transition_to! method.
AbdmLinkRequest
initiated --> auth_pending --> auth_confirmed --> token_generated --> contexts_adding --> completed
\-> failed (auth denied)
Any active state --> expired (TTL)
Any active state --> failed (error)| From | Allowed Transitions |
|---|---|
initiated | auth_pending, failed, expired |
auth_pending | auth_confirmed, failed, expired |
auth_confirmed | token_generated, failed, expired |
token_generated | contexts_adding, failed, expired |
contexts_adding | completed, failed, expired |
Terminal statuses: completed, failed, expired
AbdmDiscoveryRequest
pending --> matched --> link_initiated --> link_confirmed
\-> not_found
Any active state --> failed (error)| From | Allowed Transitions |
|---|---|
pending | matched, not_found, failed |
matched | link_initiated, failed |
link_initiated | link_confirmed, failed |
Terminal statuses: link_confirmed, not_found, failed
AbdmCareContext (sync_status)
linked --> pending_transfer --> transferred
\-> failed
\-> expiredNote: This uses simple status updates rather than a formal transition_to! method.
AbdmConsent
requested --> granted --> revoked
\-> denied
granted --> expired (TTL or consent_expiry reached)| From | Allowed Transitions |
|---|---|
requested | granted, denied |
granted | revoked, expired |
Terminal statuses: denied, revoked, expired
Every transition is automatically logged to AbdmConsentAuditLog with triggered_by (abdm_callback, user_action, system_expiry).
AbdmConsentArtefact (fetch_status)
pending --> fetching --> fetched
\-> failed
fetched --> erased (data erasure compliance)AbdmDataTransfer
HIP (outbound):
requested --> acknowledged --> key_generated --> encrypting --> transferring --> completedHIU (inbound):
requested --> acknowledged --> key_generated --> transferring --> completedAny active state can transition to failed or partial.
| From | Allowed Transitions |
|---|---|
requested | acknowledged, failed |
acknowledged | key_generated, failed |
key_generated | encrypting, transferring, failed |
encrypting | transferring, failed |
transferring | completed, failed, partial |
Terminal statuses: completed, failed, partial
AbdmCallback
received --> processing --> processed
\-> failed --> dead_letter (after max_attempts)Retry uses exponential backoff: 30s, 60s, 120s, etc.
Security
Row-Level Security (RLS)
All integration tables (except abdm_hip_registry) have standard tenant isolation RLS:
ALTER TABLE integration.[table] ENABLE ROW LEVEL SECURITY;
ALTER TABLE integration.[table] FORCE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON integration.[table]
FOR ALL
USING (tenant_id = NULLIF(current_setting('app.current_tenant_id', true), '')::uuid)
WITH CHECK (tenant_id = NULLIF(current_setting('app.current_tenant_id', true), '')::uuid);The abdm_hip_registry table has no RLS because it is queried during webhook pre-authentication before tenant context is established. It is a read-only lookup table synced automatically via a database trigger from abdm_facility_registrations.
Webhook Authentication
ABDM webhooks are authenticated via JWT signature verification using the WebhookAuthenticator:
- JWT verification: Token from
Authorization: Bearer <token>header is verified against ABDM's JWKS endpoint using RS256 algorithm. - JWKS caching: Public keys are cached for 1 hour (
JWKS_CACHE_TTL). On verification failure, keys are automatically refreshed (handles key rotation). - Replay protection: Request timestamps must be within a 5-minute window (
REPLAY_WINDOW). - Standard auth skipped:
WebhookBaseControllerskipsrequire_authentication!,set_tenant_from_session, andset_rls_contextfrom the normal controller chain.
PII Encryption
Two models encrypt sensitive JSONB fields at the application level using Rails encrypts:
AbdmPatientProfile.profile_data-- Contains name, DOB, gender, addressAbdmAuthSession.step_data-- Contains suggested addresses, partial identifiers
ECDH Encryption (Fidelius)
All health data transfers use ECDH curve25519 encryption via an external Fidelius HTTP sidecar service:
| Operation | Endpoint | Description |
|---|---|---|
generate_key_pair | GET /keys/generate | Generate ECDH key pair (publicKey, privateKey, nonce) |
encrypt | POST /encrypt | Encrypt FHIR JSON with sender/receiver ECDH keys |
decrypt | POST /decrypt | Decrypt ciphertext with sender/receiver ECDH keys |
The service includes a circuit breaker (3 failures / 30s timeout). Base URL is configured via credentials.abdm.fidelius_url (default: http://localhost:8090).
HIP data transfer flow:
- Acknowledge data request to ABDM Gateway
- Generate ECDH sender keys via Fidelius
- Encrypt each care context's FHIR bundle
- Push encrypted data to HIU's
dataPushUrl - Notify ABDM of transfer completion with per-context status
HIU data fetch flow:
- Generate ECDH receiver keys via Fidelius
- Send data request to ABDM with key material and
dataPushUrl - Receive encrypted data pushed by HIP
- Decrypt using Fidelius and store FHIR records in
AbdmConsentArtefact
Circuit Breakers
Both external service clients include circuit breakers to handle outages gracefully:
| Client | Threshold | Timeout | Description |
|---|---|---|---|
ApiClient | 5 consecutive failures | 60 seconds | Opens on ABDM Gateway failures |
FideliusService | 3 consecutive failures | 30 seconds | Opens on Fidelius sidecar failures |
Immutable Audit Log
AbdmConsentAuditLog is an append-only table for regulatory compliance:
- PostgreSQL RULEs prevent UPDATE and DELETE operations
- The model returns
readonly?for persisted records - Every consent state transition automatically creates an audit entry
Data Erasure Compliance
Mandatory compliance with ABDM data_erase_at dates. The DataErasureJob runs daily and erases:
| Table | Columns Erased |
|---|---|
abdm_consent_artefacts | fhir_records, care_contexts (sets fetch_status='erased') |
abdm_consents | consent_artifact JSONB |
abdm_care_contexts | fhir_bundle JSONB |
Frontend Integration
TypeScript Types
File: frontend/apps/cms/src/types/abdm.ts
Defines all type interfaces for the ABDM integration, organized by phase. Each entity has a base interface and a Detailed variant with additional fields:
| Type | Phase | Description |
|---|---|---|
AbdmFacilityRegistration / Detailed | 1 | HIP/HIU facility registrations |
AbdmCallback / Detailed | 1 | Webhook callback records |
AbdmPatientProfile / Detailed | 2 | ABHA patient identity |
AbdmAuthSession / Detailed | 2 | Auth and registration sessions |
AbdmLinkRequest / Detailed | 3 | HIP-initiated link requests |
AbdmDiscoveryRequest / Detailed | 3 | Patient-initiated discovery |
AbdmCareContext / Detailed | 3 | Care contexts with FHIR data |
AbdmConsent / Detailed | 4 | Consent lifecycle |
AbdmConsentArtefact / Detailed | 4 | Consent artefacts |
AbdmDataTransfer / Detailed | 4 | Encrypted data transfers |
Union types for status values (matching backend constants exactly):
type AbdmConsentStatus = 'requested' | 'granted' | 'denied' | 'revoked' | 'expired'
type AbdmLinkRequestStatus = 'initiated' | 'auth_pending' | 'auth_confirmed' | 'token_generated' | 'contexts_adding' | 'completed' | 'failed' | 'expired'
type AbdmDataTransferStatus = 'requested' | 'acknowledged' | 'key_generated' | 'encrypting' | 'transferring' | 'completed' | 'failed' | 'partial'Request parameter types: AbdmConsentCreateParams, AbdmFacilityRegistrationCreateParams, AbdmLinkRequestCreateParams, AbdmAbhaRegistrationParams
Generic API response wrapper:
interface AbdmApiResponse<T> {
data: T
meta?: { total: number; page: number; per_page: number }
}Constants
File: frontend/apps/cms/src/constants/abdm.ts
Maps backend enum values to display labels and ABDM specification codes.
Consent purpose options (with ABDM codes):
ABDM_PURPOSE_OPTIONS = [
{ value: 'care_management', code: 'CAREMGT', label: 'Care Management' },
{ value: 'break_the_glass', code: 'BTG', label: 'Break The Glass' },
{ value: 'public_health', code: 'PUBHLTH', label: 'Public Health' },
{ value: 'healthcare_payment', code: 'HPAYMT', label: 'Healthcare Payment' },
{ value: 'disease_research', code: 'DSRCH', label: 'Disease Specific Healthcare Research' },
{ value: 'patient_requested', code: 'PATRQT', label: 'Patient Request' },
]Health information types:
ABDM_HI_TYPES = [
{ code: 'OPConsultation', label: 'OP Consultation' },
{ code: 'DiagnosticReport', label: 'Diagnostic Reports' },
{ code: 'DischargeSummary', label: 'Discharge Summary' },
{ code: 'Prescription', label: 'Prescription' },
{ code: 'ImmunizationRecord', label: 'Immunization Record' },
{ code: 'HealthDocumentRecord', label: 'Health Document Record' },
]Terminal status arrays (for UI state checks):
ABDM_CONSENT_TERMINAL_STATUSES: ['denied', 'revoked', 'expired']
ABDM_TRANSFER_TERMINAL_STATUSES: ['completed', 'failed', 'partial']
ABDM_LINK_TERMINAL_STATUSES: ['completed', 'failed', 'expired']
ABDM_DISCOVERY_TERMINAL_STATUSES: ['link_confirmed', 'not_found', 'failed']API Endpoint Paths
All paths are defined in ABDM_API_PATHS for use with the shared API client:
ABDM_API_PATHS = {
// Phase 1
facilityRegistrations: '/integration/abdm/facility_registrations',
// Phase 2
abhaRegistrationSendOtp: '/integration/abdm/abha/registrations/send_otp',
abhaRegistrationVerifyOtp: '/integration/abdm/abha/registrations/verify_otp',
abhaRegistrationSuggestAddress: '/integration/abdm/abha/registrations/suggest_address',
abhaRegistrationCreateAddress: '/integration/abdm/abha/registrations/create_address',
abhaProfiles: '/integration/abdm/abha/profiles',
phrSessionSendOtp: '/integration/abdm/phr/sessions/send_otp',
phrSessionVerifyOtp: '/integration/abdm/phr/sessions/verify_otp',
phrSessionVerifyUser: '/integration/abdm/phr/sessions/verify_user',
phrSessionRefreshToken: '/integration/abdm/phr/sessions/refresh_token',
// Phase 3
linkRequests: '/integration/abdm/link_requests',
linkRequestsFetchModes: '/integration/abdm/link_requests/fetch_modes',
linkRequestsAuthInit: '/integration/abdm/link_requests/auth_init',
linkRequestsAuthConfirm: '/integration/abdm/link_requests/auth_confirm',
linkRequestsAddContexts: '/integration/abdm/link_requests/add_contexts',
careContexts: '/integration/abdm/care_contexts',
// Phase 4
consents: '/integration/abdm/consents',
healthRecords: '/integration/abdm/health_records',
cmConsents: '/integration/abdm/cm/consents',
cmArtefacts: '/integration/abdm/cm/artefacts',
}API Client
File: frontend/apps/cms/src/services/abdmApiV2.ts
Wraps all ABDM endpoints using the shared API client. Methods map 1:1 to the paths above. Used by the React Query hooks below.
React Query Hooks
Queries (frontend/apps/cms/src/hooks/useAbdmV2Queries.ts):
| Hook | Endpoint | Purpose |
|---|---|---|
useAbdmFacilityRegistrations | GET facility_registrations | List HIP/HIU registrations |
useAbdmConsents | GET consents | List consents (filterable by patient_id, direction) |
useAbdmConsent | GET consents/:id | Single consent detail |
useAbdmHealthRecords | GET health_records | Fetched FHIR records |
useAbdmCmArtefacts | GET cm/artefacts | Consent artefacts |
useAbdmLinkRequests | GET link_requests | Link request list |
useAbdmCareContexts | GET care_contexts | Care context list |
All queries are facility-scoped via currentFacilityId from useProfile() and use 30-second stale time.
Mutations (frontend/apps/cms/src/hooks/useAbdmV2Mutations.ts):
| Hook | Endpoint | Purpose |
|---|---|---|
useSendAbhaOtp | POST registrations/send_otp | Start Aadhaar OTP flow |
useVerifyAbhaOtp | POST registrations/verify_otp | Verify Aadhaar OTP |
useSuggestAbhaAddress | POST registrations/suggest_address | Get address suggestions |
useCreateAbhaAddress | POST registrations/create_address | Finalize ABHA creation |
useFetchAuthModes | POST link_requests/fetch_modes | Get available auth modes |
useAuthInit | POST link_requests/auth_init | Initiate HIP auth |
useAuthConfirm | POST link_requests/auth_confirm | Confirm OTP for linking |
useAddContexts | POST link_requests/add_contexts | Share care contexts |
useCreateConsent | POST consents | Create HIU consent request |
useApproveCmConsent | POST cm/consents/:id/approve | Approve HIP consent |
useDenyCmConsent | POST cm/consents/:id/deny | Deny HIP consent |
useRevokeCmConsent | POST cm/consents/:id/revoke | Revoke HIP consent |
All mutations invalidate relevant query caches on success.
Query keys are namespaced under cmsQueryKeys.abdmV2 in frontend/apps/cms/src/lib/queryKeys.ts.
UI Components
All ABDM UI components live in frontend/apps/cms/src/components/abdm/.
ABHA Registration Components
AbhaProfileCard -- Displays a patient's ABHA profile with formatted number (XX-XXXX-XXXX-XXXX), color-coded status badge, and PHR linked status.
<AbhaProfileCard profile={abhaProfile} />AbhaRegistrationFlow -- Multi-step wizard for creating a new ABHA via Aadhaar OTP. Steps: enter Aadhaar (auto-formatted, masked after send) -> verify OTP (60s resend timer) -> select ABHA address (API suggestions + custom) -> success.
<AbhaRegistrationFlow
onComplete={(profile) => handleCreated(profile)}
onCancel={() => close()}
/>AbhaLinkButton -- Dropdown action button. Shows "Register new ABHA" and "Link via HIP" when no active ABHA exists; shows AbhaProfileCard when ABHA is active.
<AbhaLinkButton
patientId={patientId}
abhaProfile={profile}
onProfileCreated={handleCreated}
onLinkComplete={handleLinked}
/>HIP Linking Components
HipLinkingModal -- Multi-step modal for clinic-initiated linking. Steps: fetch auth modes -> select mode (MOBILE_OTP/AADHAAR_OTP) -> OTP verification -> care context selection (checkboxes) -> success.
<HipLinkingModal
open={isOpen}
onOpenChange={setIsOpen}
patientId={patientId}
abhaAddress="user@abdm"
onLinkComplete={() => refetch()}
/>CareContextList -- Filterable list of care contexts linked to a patient. Shows reference, display text, resource type, date, and sync status.
<CareContextList patientId={patientId} />LinkingStatusBadge -- Color-coded badge for AbdmLinkRequestStatus. Green for completed, blue for in-progress, red for failed/expired.
<LinkingStatusBadge status="contexts_linked" />Health Record Components
HealthRecordViewer -- Tabbed viewer for FHIR records. Groups records by resourceType with specialized sub-renderers:
| Resource Type | Fields Displayed |
|---|---|
| MedicationRequest | Drug name, dosage, frequency, prescriber, date, status |
| Condition | Diagnosis name, clinical status, onset date, severity |
| Observation | Type, value + unit, interpretation, date, status |
| Encounter | Type/class, facility, participants, date, status |
| DiagnosticReport | Test name, conclusion, performer, date, status |
| (other) | Resource type, date, status (generic fallback) |
FHIR records are typed as Record<string, unknown>[]. Safe extraction helpers handle missing/malformed fields.
<HealthRecordViewer records={fhirRecords} />DataFetchStatus -- Progress stepper showing data transfer pipeline stages: requested -> acknowledged -> key_generated -> encrypting -> transferring -> completed. Shows timestamps, progress count, and error details with retry.
<DataFetchStatus transfer={dataTransfer} onRetry={handleRetry} />Consent Management Components (Patient Detail)
AbdmConsentsTabV2 -- Consent list with direction filter (All/HIP/HIU), table display, and actions (approve, deny, revoke for HIP consents).
ConsentRequestModalV2 -- Modal for creating new HIU consent requests with purpose, date range, HI types, and expiry.
ConsentDetailModalV2 -- Modal showing consent details with context-sensitive actions.
Real-Time Events
File: frontend/apps/cms/src/hooks/useAbdmRealtimeEvents.ts
Subscribes to the private-abdm-{facilityId} Ably channel (defined as CHANNEL_NAMES.ABDM in packages/api-client/src/services/realtime/types.ts).
| Event | Cache Invalidated |
|---|---|
abdm.link_request.updated | Link requests, patient profiles |
abdm.care_context.created | Care contexts |
abdm.consent.updated | Consents, CM artefacts |
abdm.data_transfer.updated | Health records |
// Activated in AbdmSectionV2 - active when ABDM tab is visible
useAbdmRealtimeEvents({ enabled: true })The backend must broadcast to this channel when these entities change. Event payloads should include at minimum the entity ID and new status.
Patient Detail Integration
File: frontend/apps/cms/src/features/patientDetail/components/AbdmSectionV2.tsx
The ABDM tab in PatientDetailPage renders AbdmSectionV2, which provides four sub-tabs:
| Sub-tab | Component | Purpose |
|---|---|---|
| Profile | AbhaProfileCard or AbhaLinkButton | View/create/link ABHA |
| Care Contexts | CareContextList | View linked care contexts |
| Consents | AbdmConsentsTabV2 | Manage consent requests |
| Health Records | Placeholder | Directs to consent flow |
The component also activates useAbdmRealtimeEvents for live cache updates.
Error Handling
ABHA errors use frontend/apps/cms/src/utils/abhaErrorHandler.ts which maps ABDM error codes to user-friendly messages via ABHA_ERROR_MAPPINGS. The isRetryableError helper identifies transient failures.
OTP flows show inline error alerts. Failed data transfers show error details from AbdmDataTransferDetailed.error_message with a retry button.
Background Jobs
All ABDM jobs are in domains/integration/jobs/abdm/ and inherit from Shared::ApplicationJob with Shared::Concerns::TenantAwareJob.
ProcessCallbackJob
File: domains/integration/jobs/abdm/process_callback_job.rb
Queue: abdm_callbacks
Retries: 3 with exponential backoff
Enqueued by WebhookBaseController after logging an inbound callback. Loads the AbdmCallback record and delegates to CallbackProcessor for idempotent routing and execution.
Integration::Abdm::ProcessCallbackJob.perform_later(callback_id)HipDataTransferJob
File: domains/integration/jobs/abdm/hip_data_transfer_job.rb
Queue: abdm
Retries: 3 with exponential backoff
Enqueued by HipDataHandler when a data request webhook is received. Encrypts FHIR bundles and pushes them to the requesting HIU via DataTransferService. Skips processing if the transfer is already in a terminal state.
Integration::Abdm::HipDataTransferJob.perform_later(data_transfer_id)HiuDataFetchJob
File: domains/integration/jobs/abdm/hiu_data_fetch_job.rb
Queue: abdm
Retries: 3 with exponential backoff
Enqueued by HiuConsentHandler when a consent artefact is received. Requests health records from the source HIP via DataFetchService. Skips processing if the artefact is already fetched or erased.
Integration::Abdm::HiuDataFetchJob.perform_later(consent_artefact_id)DataErasureJob
File: domains/integration/jobs/abdm/data_erasure_job.rb
Queue: abdm
Schedule: Daily (via Sidekiq cron)
Periodic compliance job that erases health data when data_erase_at is reached. Processes three categories:
- Consent artefacts: Erases
fhir_recordsandcare_contexts, setsfetch_status='erased' - Consents: Nulls
consent_artifactJSONB on granted/revoked consents past erasure date - Care contexts: Nulls
fhir_bundleJSONB on transferred/expired contexts linked to expired consents
Integration::Abdm::DataErasureJob.perform_laterMigration from Legacy
The new ABDM integration under domains/integration/ replaces the legacy implementation in parts/abdm/. This section documents the key differences.
Structural Changes
| Aspect | Legacy (parts/abdm/) | New (domains/integration/) |
|---|---|---|
| Module path | Abdm::* | Integration::Abdm* for models, Integration::Abdm::* for services |
| Schema | Default public schema | integration PostgreSQL schema |
| Primary keys | Auto-increment integers | UUIDv7 |
| Tenant isolation | Application-level clinic_id scoping | RLS with tenant_id column |
| Timestamps | TIMESTAMP | TIMESTAMPTZ |
| Base class | ApplicationRecord | Integration::BaseRecord |
Model Consolidation
| Legacy Table(s) | New Model | Notes |
|---|---|---|
abdm_services | AbdmFacilityRegistration | Renamed, added AbdmHipRegistry lookup table |
clinic_patients (ABHA fields) | AbdmPatientProfile | Extracted to dedicated model with encrypted PII |
hip_link_requests + hip_link_token_requests | AbdmLinkRequest | Consolidated since V3 uses unified flow |
hip_patient_requests | AbdmDiscoveryRequest | Renamed, added patient demographics JSONB |
hip_care_contexts | AbdmCareContext | Added FHIR bundle storage, environment-aware references |
abdm_consent_requests | AbdmConsent | Unified HIP/HIU directions in single model |
abdm_webhooks | AbdmCallback | Renamed, added idempotent processing pipeline |
abdm_requests | AbdmApiLog | Renamed, added duration tracking and retry count |
| (new) | AbdmAuthSession | Extracted multi-step auth flow from frontend state |
| (new) | AbdmConsentArtefact | Separated artefact storage from consent |
| (new) | AbdmDataTransfer | Dedicated transfer tracking with ECDH key material |
| (new) | AbdmConsentAuditLog | Immutable regulatory audit trail |
| (new) | AbdmGatewayToken | Per-tenant token management |
Architecture Improvements
| Improvement | Legacy Behavior | New Behavior |
|---|---|---|
| Webhook processing | sleep 2.seconds before processing | Async via ProcessCallbackJob with atomic claim |
| Idempotency | No dedup protection | Idempotency key with database uniqueness constraint |
| Dead letter | Failures silently lost | Dead letter queue after max_attempts failures |
| Token management | Shared across tenants | Per-tenant with lazy refresh and 5-minute pre-expiry window |
| API logging | Minimal | Full request/response logging with duration, sanitized headers |
| Circuit breaker | None | Both ApiClient (5/60s) and FideliusService (3/30s) |
| Data encryption | Application-level for some fields | Rails encrypts for PII + ECDH via Fidelius for data transfer |
| Audit trail | None for consents | Immutable append-only AbdmConsentAuditLog |
| Data erasure | Manual | Automated daily via DataErasureJob |
| Auth sessions | State managed in frontend | Server-side AbdmAuthSession with TTL and encrypted step data |
| FHIR bundles | Generated per request | Stored in AbdmCareContext.fhir_bundle for reuse |
Credential Configuration
ABDM credentials are stored in Rails credentials under the abdm namespace:
abdm:
client_id: "..."
client_secret: "..."
gateway_url: "https://dev.abdm.gov.in" # or production URL
cm_id: "sbx" # or production CM ID
jwks_url: "https://dev.abdm.gov.in/gateway/v0.5/certs"
fidelius_url: "http://localhost:8090" # Fidelius sidecar URLSee Also
- Schema Overview -- Full domain schema architecture
- Notification System -- How to send notifications from the integration domain
- Teleconsultation -- Another domain integration example