Kulcare Docs
General

ABDM Integration

Purpose: ABDM (Ayushman Bharat Digital Mission) integration for Indian healthcare digital health records, ported from legacy parts/abdm to the new domain-driven architecture under domains/integration/. Schema: integration (PostgreSQL) Tenant Scoped: Yes (all tables except abdm_hip_registry) RLS Enabled: Yes Primary Keys: UUIDv7 Timestamps: TIMESTAMPTZ


Table of Contents

  1. Overview
  2. Architecture
  3. Database Schema
  4. API Endpoints
  5. Webhook Flow
  6. State Machines
  7. Security
  8. Frontend Integration
  9. Background Jobs
  10. 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:

PhaseScopeModels
Phase 1: InfrastructureFacility registration, gateway tokens, API logging, webhook callbacksAbdmFacilityRegistration, AbdmHipRegistry, AbdmGatewayToken, AbdmApiLog, AbdmCallback
Phase 2: ABHA RegistrationPatient ABHA creation, PHR login, profile managementAbdmPatientProfile, AbdmAuthSession
Phase 3: HIP LinkingHIP-initiated linking, patient-initiated discovery, care context managementAbdmLinkRequest, AbdmDiscoveryRequest, AbdmCareContext
Phase 4: Consent & DataConsent lifecycle, artefacts, encrypted data transfer, audit loggingAbdmConsent, 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 Callbacks

File Locations

LayerLocationResponsibility
Modelsdomains/integration/models/Data persistence, validations, state machines
Controllersdomains/integration/controllers/abdm/REST API and webhook endpoints
Servicesdomains/integration/services/abdm/Business logic, API calls, encryption
Jobsdomains/integration/jobs/abdm/Async processing (callbacks, data transfer, erasure)
Blueprintsdomains/integration/blueprints/JSON serialization (normal/detailed views)
Policiesdomains/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.'
end

TenantScoped: 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

ServiceFileResponsibility
GatewayTokenServiceservices/abdm/gateway_token_service.rbPer-tenant JWT token management with lazy refresh
ApiClientservices/abdm/api_client.rbHTTP client with retry, circuit breaker, logging
CallbackProcessorservices/abdm/callback_processor.rbIdempotent webhook routing and processing
WebhookAuthenticatorservices/abdm/webhook_authenticator.rbABDM JWT signature verification with JWKS
FideliusServiceservices/abdm/encryption/fidelius_service.rbECDH curve25519 encryption via sidecar
RsaEncryptionServiceservices/abdm/encryption/rsa_encryption_service.rbRSA encryption for Aadhaar data
RegistrationServiceservices/abdm/abha/registration_service.rbABHA registration (Aadhaar/mobile OTP)
ProfileServiceservices/abdm/abha/profile_service.rbABHA profile fetch, QR code, card
LoginServiceservices/abdm/phr/login_service.rbPHR login and token management
LinkServiceservices/abdm/hip/link_service.rbHIP-initiated linking workflow
PatientMatcherservices/abdm/hip/patient_matcher.rbPatient discovery matching logic
DiscoveryServiceservices/abdm/hip/discovery_service.rbPatient-initiated discovery handling
DataTransferServiceservices/abdm/hip/data_transfer_service.rbHIP data encryption and push
ConsentRequestServiceservices/abdm/hiu/consent_request_service.rbHIU consent request creation
DataFetchServiceservices/abdm/hiu/data_fetch_service.rbHIU data request and decryption
Cm::ConsentServiceservices/abdm/cm/consent_service.rbPatient consent approve/deny/revoke
Cm::ArtefactServiceservices/abdm/cm/artefact_service.rbConsent artefact management
BundleBuilderservices/abdm/fhir/bundle_builder.rbFHIR R4 document bundle construction
MedicationRequestBundleservices/abdm/fhir/medication_request_bundle.rbPrescription FHIR bundles
EncounterBundleservices/abdm/fhir/encounter_bundle.rbEncounter FHIR bundles

Webhook Handlers

HandlerFileCallbacks Handled
HipLinkHandlerservices/abdm/webhooks/hip_link_handler.rbon_token_generate, on_add_contexts, auth_notify, profile_share
HipDiscoveryHandlerservices/abdm/webhooks/hip_discovery_handler.rbdiscover, link_init, link_confirm
HipConsentHandlerservices/abdm/webhooks/hip_consent_handler.rbconsent_notify (HIP side)
HipDataHandlerservices/abdm/webhooks/hip_data_handler.rbon_request (data request from HIU)
HiuConsentHandlerservices/abdm/webhooks/hiu_consent_handler.rbconsent_notify, on_fetch (HIU side)
HiuDataHandlerservices/abdm/webhooks/hiu_data_handler.rbdata_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.

ColumnTypeRequiredDescription
idUUIDYesUUIDv7 primary key
tenant_idUUIDYesTenant isolation
providerVARCHAR(20)YesProvider identifier (default: abdm_v3)
access_tokenTEXTYesJWT access token
expires_atTIMESTAMPTZYesToken expiration
refresh_tokenTEXTNoRefresh token
refresh_expires_atTIMESTAMPTZNoRefresh 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.

ColumnTypeRequiredDescription
idUUIDYesUUIDv7 primary key
tenant_idUUIDYesTenant isolation
facility_idUUIDYesFK to identity.facilities
registration_typeVARCHARYesHIP or HIU
service_idVARCHAR(100)YesABDM-assigned service ID (globally unique)
service_nameVARCHAR(255)YesRegistered service name
callback_urlVARCHAR(500)NoWebhook callback base URL
activeBOOLEANYesRegistration 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.

ColumnTypeRequiredDescription
service_idVARCHAR(100)YesPrimary key (ABDM service ID)
tenant_idUUIDYesFK to identity.tenants
facility_idUUIDYesFK to identity.facilities
registration_typeVARCHARYesHIP or HIU
activeBOOLEANYesWhether 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.

ColumnTypeRequiredDescription
idUUIDYesUUIDv7 primary key
tenant_idUUIDYesTenant isolation
request_idVARCHAR(100)YesABDM request ID
urlVARCHAR(500)YesAPI endpoint URL
http_methodVARCHAR(10)YesHTTP method
request_headersJSONBNoSanitized request headers
request_payloadJSONBNoRequest body
response_statusINTEGERNoHTTP response status code
response_bodyJSONBNoResponse body
successBOOLEANNoWhether request succeeded
duration_msINTEGERNoResponse time in milliseconds
retry_countINTEGERNoNumber of retry attempts
error_messageTEXTNoError description

integration.abdm_callbacks

Inbound webhook payload audit log with idempotent processing pipeline.

ColumnTypeRequiredDescription
idUUIDYesUUIDv7 primary key
tenant_idUUIDYesTenant isolation
facility_idUUIDYesFK to identity.facilities
callback_typeVARCHAR(100)YesHandler routing key
request_idVARCHARNoABDM request ID
transaction_idVARCHARNoABDM transaction ID
payloadJSONBYesFull webhook payload
statusVARCHARYesreceived, processing, processed, failed, dead_letter
idempotency_keyVARCHAR(250)NoDedup key: {callback_type}:{request_id}
processing_attemptsINTEGERNoNumber of processing attempts
max_attemptsINTEGERNoMaximum retry attempts
next_retry_atTIMESTAMPTZNoNext retry time (exponential backoff)
error_codeVARCHARNoABDM error code
error_messageTEXTNoError description
processed_atTIMESTAMPTZNoWhen 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.

ColumnTypeRequiredDescription
idUUIDYesUUIDv7 primary key
tenant_idUUIDYesTenant isolation
patient_idUUIDYesFK to clinical.patients (unique per tenant)
abha_numberVARCHAR(14)No14-digit ABHA number (e.g., 91-1234-5678-9012)
abha_addressVARCHAR(100)NoABHA address (e.g., user@abdm)
health_idVARCHAR(100)NoLegacy health ID
registration_statusVARCHARYesSee state machine section
profile_dataJSONBNoEncrypted PII (name, DOB, gender, address)
phr_linkedBOOLEANNoWhether 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.

ColumnTypeRequiredDescription
idUUIDYesUUIDv7 primary key
tenant_idUUIDYesTenant isolation
facility_idUUIDYesFK to identity.facilities
patient_idUUIDNoFK to clinical.patients (set after match)
initiated_by_idUUIDNoFK to identity.users
session_typeVARCHARYesSee session types below
statusVARCHARYesCurrent state
txn_idVARCHAR(100)NoABDM transaction ID
step_dataJSONBNoEncrypted step-specific data
expires_atTIMESTAMPTZYesSession TTL
completed_atTIMESTAMPTZNoWhen 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

HIP-initiated linking flow. Consolidates legacy hip_link_requests + hip_link_token_requests since V3 uses a unified flow.

ColumnTypeRequiredDescription
idUUIDYesUUIDv7 primary key
tenant_idUUIDYesTenant isolation
facility_idUUIDYesFK to identity.facilities
patient_idUUIDYesFK to clinical.patients
abdm_patient_profile_idUUIDNoFK to abdm_patient_profiles
statusVARCHARYesCurrent state (see state machine)
abha_numberVARCHAR(14)NoPatient's ABHA number
abha_addressVARCHAR(100)NoPatient's ABHA address
auth_modeVARCHAR(20)NoAuthentication mode used
link_tokenTEXTNoABDM-issued link token
link_token_expires_atTIMESTAMPTZNoToken expiration
abdm_api_log_idUUIDNoFK to last API call log
abdm_callback_idUUIDNoFK 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.

ColumnTypeRequiredDescription
idUUIDYesUUIDv7 primary key
tenant_idUUIDYesTenant isolation
facility_idUUIDYesFK to identity.facilities
patient_idUUIDNoFK to clinical.patients (set after match)
transaction_idVARCHAR(100)YesABDM transaction ID
abha_addressVARCHAR(100)NoPatient's ABHA address
patient_demographicsJSONBYesName, gender, yearOfBirth, identifiers
verification_codeVARCHAR(10)No6-digit code for link confirmation
statusVARCHARYesCurrent state
abdm_callback_idUUIDNoFK 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}
ColumnTypeRequiredDescription
idUUIDYesUUIDv7 primary key
tenant_idUUIDYesTenant isolation
facility_idUUIDYesFK to identity.facilities
patient_idUUIDYesFK to clinical.patients
abdm_patient_profile_idUUIDNoFK to abdm_patient_profiles
link_request_idUUIDNoFK to abdm_link_requests (HIP-initiated)
discovery_request_idUUIDNoFK to abdm_discovery_requests (patient-initiated)
care_context_referenceVARCHAR(50)YesUnique environment-aware reference
care_context_displayVARCHAR(255)YesHuman-readable name (e.g., CAREPLAN-07-January-2025)
resource_typeVARCHARYesPrescription, Encounter, DiagnosticReport
resource_idUUIDYesID of the clinical resource
resource_schemaVARCHAR(20)YesSchema where resource lives (e.g., clinical)
sync_statusVARCHARYesSee state machine section
fhir_bundleJSONBNoFHIR R4 document bundle for data transfer

Key methods: self.generate_reference(type, id), self.generate_display(type, created_at)

integration.abdm_consents

Unified consent lifecycle for both HIP and HIU directions. Status transitions are automatically logged to AbdmConsentAuditLog.

ColumnTypeRequiredDescription
idUUIDYesUUIDv7 primary key
tenant_idUUIDYesTenant isolation
facility_idUUIDYesFK to identity.facilities
patient_idUUIDYesFK to clinical.patients
abdm_patient_profile_idUUIDNoFK to abdm_patient_profiles
directionVARCHARYeship or hiu
statusVARCHARYesSee state machine section
purposeVARCHARYescare_management, break_the_glass, public_health, healthcare_payment, disease_research, patient_requested
hi_typesTEXT[]YesHealth information types (e.g., OPConsultation, Prescription)
consent_idVARCHAR(100)NoABDM consent ID
requester_idVARCHAR(100)NoRequesting HIP/HIU service ID
date_range_fromTIMESTAMPTZNoData access period start
date_range_toTIMESTAMPTZNoData access period end
consent_expiryTIMESTAMPTZNoWhen consent expires
data_erase_atTIMESTAMPTZNoWhen data must be erased
granted_atTIMESTAMPTZNoWhen consent was granted
denied_atTIMESTAMPTZNoWhen consent was denied
revoked_atTIMESTAMPTZNoWhen consent was revoked
requested_by_idUUIDNoFK to identity.users
consent_artifactJSONBNoRaw consent artifact (erased on data_erase_at)
abdm_callback_idUUIDNoFK to originating callback

Associations: has_many :consent_artefacts, has_many :data_transfers, has_many :audit_logs

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.

ColumnTypeRequiredDescription
idUUIDYesUUIDv7 primary key
tenant_idUUIDYesTenant isolation
abdm_consent_idUUIDYesFK to abdm_consents
artefact_idVARCHAR(100)YesABDM artefact ID (globally unique)
fetch_statusVARCHARYespending, fetching, fetched, failed, erased
care_contextsJSONBNoCare context references
fhir_recordsJSONBNoDecrypted FHIR records (erased on compliance)
data_erase_atTIMESTAMPTZNoWhen records must be erased
fetched_atTIMESTAMPTZNoWhen FHIR records were fetched
erased_atTIMESTAMPTZNoWhen 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.

ColumnTypeRequiredDescription
idUUIDYesUUIDv7 primary key
tenant_idUUIDYesTenant isolation
facility_idUUIDYesFK to identity.facilities
abdm_consent_idUUIDYesFK to abdm_consents
consent_artefact_idUUIDNoFK to abdm_consent_artefacts
directionVARCHARYeship or hiu
statusVARCHARYesSee state machine section
transaction_idVARCHAR(100)NoABDM transaction ID
data_push_urlVARCHAR(500)NoURL for HIP to push data
key_materialJSONBNoECDH key pairs (sender/receiver keys, nonces)
total_contextsINTEGERNoTotal care contexts to transfer
transferred_contextsINTEGERNoCare contexts successfully transferred
error_codeVARCHAR(50)NoError code
error_messageTEXTNoError description
started_atTIMESTAMPTZNoWhen encryption/transfer started
completed_atTIMESTAMPTZNoWhen transfer completed/failed

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

ColumnTypeRequiredDescription
idUUIDYesUUIDv7 primary key
tenant_idUUIDYesTenant isolation
abdm_consent_idUUIDYesFK to abdm_consents
previous_statusVARCHARNoStatus before transition
new_statusVARCHARYesStatus after transition
triggered_byVARCHARYesabdm_callback, user_action, system_expiry
triggered_by_idVARCHARNoUser ID or callback ID
change_reasonTEXTNoHuman-readable reason
created_atTIMESTAMPTZYesWhen 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

MethodPathControllerDescription
GET/integration/abdm/facility_registrationsFacilityRegistrationsController#indexList facility registrations
POST/integration/abdm/facility_registrationsFacilityRegistrationsController#createRegister facility as HIP/HIU
GET/integration/abdm/facility_registrations/:idFacilityRegistrationsController#showGet registration details
PATCH/integration/abdm/facility_registrations/:idFacilityRegistrationsController#updateUpdate registration

Phase 2: ABHA Registration and PHR Login

ABHA Registration (Aadhaar OTP flow):

MethodPathDescription
POST/integration/abdm/abha/registrations/send_otpSend Aadhaar or mobile OTP
POST/integration/abdm/abha/registrations/verify_otpVerify OTP and progress session
POST/integration/abdm/abha/registrations/suggest_addressGet suggested ABHA addresses
POST/integration/abdm/abha/registrations/create_addressCreate ABHA address and finalize

ABHA Profile Management:

MethodPathDescription
GET/integration/abdm/abha/profiles/:idGet patient's ABHA profile
POST/integration/abdm/abha/profiles/:id/fetch_profileRefresh profile from ABDM
GET/integration/abdm/abha/profiles/:id/qr_codeGet ABHA QR code
GET/integration/abdm/abha/profiles/:id/cardGet ABHA card image

PHR Session Management:

MethodPathDescription
POST/integration/abdm/phr/sessions/send_otpSend PHR login OTP
POST/integration/abdm/phr/sessions/verify_otpVerify PHR login OTP
POST/integration/abdm/phr/sessions/verify_userVerify user identity
POST/integration/abdm/phr/sessions/refresh_tokenRefresh PHR session token

Phase 3: HIP Linking and Care Contexts

MethodPathDescription
GET/integration/abdm/link_requestsList link requests
GET/integration/abdm/link_requests/:idGet link request details
POST/integration/abdm/link_requests/fetch_modesFetch available auth modes for patient
POST/integration/abdm/link_requests/auth_initInitiate auth for linking
POST/integration/abdm/link_requests/auth_confirmConfirm auth with OTP/token
POST/integration/abdm/link_requests/add_contextsAdd care contexts to link
GET/integration/abdm/care_contextsList care contexts
GET/integration/abdm/care_contexts/:idGet care context details

Consent (HIP/HIU):

MethodPathDescription
GET/integration/abdm/consentsList consents
POST/integration/abdm/consentsCreate HIU consent request
GET/integration/abdm/consents/:idGet consent details
GET/integration/abdm/health_recordsList health records
GET/integration/abdm/health_records/:idGet health record details

Consent Management (patient-facing CM operations):

MethodPathDescription
POST/integration/abdm/cm/consents/:id/approveApprove a consent request
POST/integration/abdm/cm/consents/:id/denyDeny a consent request
POST/integration/abdm/cm/consents/:id/revokeRevoke a granted consent
GET/integration/abdm/cm/artefactsList consent artefacts
GET/integration/abdm/cm/artefacts/:idGet artefact details

Webhooks (ABDM JWT Auth, No User Session)

MethodPathControllerDescription
GET/integration/abdm/webhooks/heartbeatHeartbeatController#showHealth check
POST/integration/abdm/webhooks/hip/on_token_generateHipLinkWebhooksControllerLink token generated callback
POST/integration/abdm/webhooks/hip/on_add_contextsHipLinkWebhooksControllerContexts added callback
POST/integration/abdm/webhooks/hip/auth_notifyHipLinkWebhooksControllerAuth notification callback
POST/integration/abdm/webhooks/hip/profile_shareHipLinkWebhooksControllerProfile share callback
POST/integration/abdm/webhooks/hip/consent_notifyHipConsentWebhooksControllerHIP consent notification
POST/integration/abdm/webhooks/hip/on_requestHipDataWebhooksControllerData request from HIU
POST/integration/abdm/webhooks/discovery/discoverHipDiscoveryWebhooksControllerPatient discovery
POST/integration/abdm/webhooks/discovery/link_initHipDiscoveryWebhooksControllerPatient link init
POST/integration/abdm/webhooks/discovery/link_confirmHipDiscoveryWebhooksControllerPatient link confirm
POST/integration/abdm/webhooks/hiu/consent_notifyHiuConsentWebhooksControllerHIU consent notification
POST/integration/abdm/webhooks/hiu/on_fetchHiuConsentWebhooksControllerHIU artefact fetch
POST/integration/abdm/webhooks/hiu/data_notificationHiuDataWebhooksControllerData 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 TypeHandler ClassMethod
hip.on_token_generateWebhooks::HipLinkHandleron_token_generate
hip.on_add_contextsWebhooks::HipLinkHandleron_add_contexts
hip.auth_notifyWebhooks::HipLinkHandlerauth_notify
hip.profile_shareWebhooks::HipLinkHandlerprofile_share
discovery.discoverWebhooks::HipDiscoveryHandlerdiscover
discovery.link_initWebhooks::HipDiscoveryHandlerlink_init
discovery.link_confirmWebhooks::HipDiscoveryHandlerlink_confirm
hip.consent_notifyWebhooks::HipConsentHandlerconsent_notify
hip.data_requestWebhooks::HipDataHandleron_request
hiu.consent_notifyWebhooks::HiuConsentHandlerconsent_notify
hiu.on_fetchWebhooks::HiuConsentHandleron_fetch
hiu.data_notificationWebhooks::HiuDataHandlerdata_notification

Idempotency

Duplicate webhook deliveries are handled at two levels:

  1. Database level: The idempotency_key column ({callback_type}:{request_id}) has a uniqueness constraint. Duplicate inserts raise ActiveRecord::RecordNotUnique, which WebhookBaseController catches and returns the existing callback.
  2. Processing level: claim! uses an atomic UPDATE WHERE status='received' so only one worker can process a callback even if multiple ProcessCallbackJob instances 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)
FromAllowed Transitions
initiatedotp_sent, expired, failed
otp_sentotp_verified, expired, failed
otp_verifiedaddress_suggested, completed, expired, failed
address_suggestedcompleted, expired, failed

AbdmPatientProfile (registration_status)

pending --> aadhaar_verified --> mobile_verified --> registered --> active
                                                                   |
                                                                   v
                                                              deactivated

Note: 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)
FromAllowed Transitions
initiatedauth_pending, failed, expired
auth_pendingauth_confirmed, failed, expired
auth_confirmedtoken_generated, failed, expired
token_generatedcontexts_adding, failed, expired
contexts_addingcompleted, failed, expired

Terminal statuses: completed, failed, expired

AbdmDiscoveryRequest

pending --> matched --> link_initiated --> link_confirmed
        \-> not_found
Any active state --> failed (error)
FromAllowed Transitions
pendingmatched, not_found, failed
matchedlink_initiated, failed
link_initiatedlink_confirmed, failed

Terminal statuses: link_confirmed, not_found, failed

AbdmCareContext (sync_status)

linked --> pending_transfer --> transferred
                            \-> failed
                            \-> expired

Note: This uses simple status updates rather than a formal transition_to! method.

AbdmConsent

requested --> granted --> revoked
          \-> denied
granted --> expired (TTL or consent_expiry reached)
FromAllowed Transitions
requestedgranted, denied
grantedrevoked, 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 --> completed

HIU (inbound):

requested --> acknowledged --> key_generated --> transferring --> completed

Any active state can transition to failed or partial.

FromAllowed Transitions
requestedacknowledged, failed
acknowledgedkey_generated, failed
key_generatedencrypting, transferring, failed
encryptingtransferring, failed
transferringcompleted, 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:

  1. JWT verification: Token from Authorization: Bearer <token> header is verified against ABDM's JWKS endpoint using RS256 algorithm.
  2. JWKS caching: Public keys are cached for 1 hour (JWKS_CACHE_TTL). On verification failure, keys are automatically refreshed (handles key rotation).
  3. Replay protection: Request timestamps must be within a 5-minute window (REPLAY_WINDOW).
  4. Standard auth skipped: WebhookBaseController skips require_authentication!, set_tenant_from_session, and set_rls_context from 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, address
  • AbdmAuthSession.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:

OperationEndpointDescription
generate_key_pairGET /keys/generateGenerate ECDH key pair (publicKey, privateKey, nonce)
encryptPOST /encryptEncrypt FHIR JSON with sender/receiver ECDH keys
decryptPOST /decryptDecrypt 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:

  1. Acknowledge data request to ABDM Gateway
  2. Generate ECDH sender keys via Fidelius
  3. Encrypt each care context's FHIR bundle
  4. Push encrypted data to HIU's dataPushUrl
  5. Notify ABDM of transfer completion with per-context status

HIU data fetch flow:

  1. Generate ECDH receiver keys via Fidelius
  2. Send data request to ABDM with key material and dataPushUrl
  3. Receive encrypted data pushed by HIP
  4. Decrypt using Fidelius and store FHIR records in AbdmConsentArtefact

Circuit Breakers

Both external service clients include circuit breakers to handle outages gracefully:

ClientThresholdTimeoutDescription
ApiClient5 consecutive failures60 secondsOpens on ABDM Gateway failures
FideliusService3 consecutive failures30 secondsOpens 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:

TableColumns Erased
abdm_consent_artefactsfhir_records, care_contexts (sets fetch_status='erased')
abdm_consentsconsent_artifact JSONB
abdm_care_contextsfhir_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:

TypePhaseDescription
AbdmFacilityRegistration / Detailed1HIP/HIU facility registrations
AbdmCallback / Detailed1Webhook callback records
AbdmPatientProfile / Detailed2ABHA patient identity
AbdmAuthSession / Detailed2Auth and registration sessions
AbdmLinkRequest / Detailed3HIP-initiated link requests
AbdmDiscoveryRequest / Detailed3Patient-initiated discovery
AbdmCareContext / Detailed3Care contexts with FHIR data
AbdmConsent / Detailed4Consent lifecycle
AbdmConsentArtefact / Detailed4Consent artefacts
AbdmDataTransfer / Detailed4Encrypted 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):

HookEndpointPurpose
useAbdmFacilityRegistrationsGET facility_registrationsList HIP/HIU registrations
useAbdmConsentsGET consentsList consents (filterable by patient_id, direction)
useAbdmConsentGET consents/:idSingle consent detail
useAbdmHealthRecordsGET health_recordsFetched FHIR records
useAbdmCmArtefactsGET cm/artefactsConsent artefacts
useAbdmLinkRequestsGET link_requestsLink request list
useAbdmCareContextsGET care_contextsCare 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):

HookEndpointPurpose
useSendAbhaOtpPOST registrations/send_otpStart Aadhaar OTP flow
useVerifyAbhaOtpPOST registrations/verify_otpVerify Aadhaar OTP
useSuggestAbhaAddressPOST registrations/suggest_addressGet address suggestions
useCreateAbhaAddressPOST registrations/create_addressFinalize ABHA creation
useFetchAuthModesPOST link_requests/fetch_modesGet available auth modes
useAuthInitPOST link_requests/auth_initInitiate HIP auth
useAuthConfirmPOST link_requests/auth_confirmConfirm OTP for linking
useAddContextsPOST link_requests/add_contextsShare care contexts
useCreateConsentPOST consentsCreate HIU consent request
useApproveCmConsentPOST cm/consents/:id/approveApprove HIP consent
useDenyCmConsentPOST cm/consents/:id/denyDeny HIP consent
useRevokeCmConsentPOST cm/consents/:id/revokeRevoke 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 TypeFields Displayed
MedicationRequestDrug name, dosage, frequency, prescriber, date, status
ConditionDiagnosis name, clinical status, onset date, severity
ObservationType, value + unit, interpretation, date, status
EncounterType/class, facility, participants, date, status
DiagnosticReportTest 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} />

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

EventCache Invalidated
abdm.link_request.updatedLink requests, patient profiles
abdm.care_context.createdCare contexts
abdm.consent.updatedConsents, CM artefacts
abdm.data_transfer.updatedHealth 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-tabComponentPurpose
ProfileAbhaProfileCard or AbhaLinkButtonView/create/link ABHA
Care ContextsCareContextListView linked care contexts
ConsentsAbdmConsentsTabV2Manage consent requests
Health RecordsPlaceholderDirects 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:

  1. Consent artefacts: Erases fhir_records and care_contexts, sets fetch_status='erased'
  2. Consents: Nulls consent_artifact JSONB on granted/revoked consents past erasure date
  3. Care contexts: Nulls fhir_bundle JSONB on transferred/expired contexts linked to expired consents
Integration::Abdm::DataErasureJob.perform_later

Migration from Legacy

The new ABDM integration under domains/integration/ replaces the legacy implementation in parts/abdm/. This section documents the key differences.

Structural Changes

AspectLegacy (parts/abdm/)New (domains/integration/)
Module pathAbdm::*Integration::Abdm* for models, Integration::Abdm::* for services
SchemaDefault public schemaintegration PostgreSQL schema
Primary keysAuto-increment integersUUIDv7
Tenant isolationApplication-level clinic_id scopingRLS with tenant_id column
TimestampsTIMESTAMPTIMESTAMPTZ
Base classApplicationRecordIntegration::BaseRecord

Model Consolidation

Legacy Table(s)New ModelNotes
abdm_servicesAbdmFacilityRegistrationRenamed, added AbdmHipRegistry lookup table
clinic_patients (ABHA fields)AbdmPatientProfileExtracted to dedicated model with encrypted PII
hip_link_requests + hip_link_token_requestsAbdmLinkRequestConsolidated since V3 uses unified flow
hip_patient_requestsAbdmDiscoveryRequestRenamed, added patient demographics JSONB
hip_care_contextsAbdmCareContextAdded FHIR bundle storage, environment-aware references
abdm_consent_requestsAbdmConsentUnified HIP/HIU directions in single model
abdm_webhooksAbdmCallbackRenamed, added idempotent processing pipeline
abdm_requestsAbdmApiLogRenamed, added duration tracking and retry count
(new)AbdmAuthSessionExtracted multi-step auth flow from frontend state
(new)AbdmConsentArtefactSeparated artefact storage from consent
(new)AbdmDataTransferDedicated transfer tracking with ECDH key material
(new)AbdmConsentAuditLogImmutable regulatory audit trail
(new)AbdmGatewayTokenPer-tenant token management

Architecture Improvements

ImprovementLegacy BehaviorNew Behavior
Webhook processingsleep 2.seconds before processingAsync via ProcessCallbackJob with atomic claim
IdempotencyNo dedup protectionIdempotency key with database uniqueness constraint
Dead letterFailures silently lostDead letter queue after max_attempts failures
Token managementShared across tenantsPer-tenant with lazy refresh and 5-minute pre-expiry window
API loggingMinimalFull request/response logging with duration, sanitized headers
Circuit breakerNoneBoth ApiClient (5/60s) and FideliusService (3/30s)
Data encryptionApplication-level for some fieldsRails encrypts for PII + ECDH via Fidelius for data transfer
Audit trailNone for consentsImmutable append-only AbdmConsentAuditLog
Data erasureManualAutomated daily via DataErasureJob
Auth sessionsState managed in frontendServer-side AbdmAuthSession with TTL and encrypted step data
FHIR bundlesGenerated per requestStored 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 URL

See Also

On this page

Table of ContentsOverviewWhat is ABDMWhy We IntegrateIntegration PhasesArchitectureDomain StructureFile LocationsKey PatternsKey ServicesWebhook HandlersDatabase SchemaPhase 1: Infrastructure Tablesintegration.abdm_gateway_tokensintegration.abdm_facility_registrationsintegration.abdm_hip_registryintegration.abdm_api_logsintegration.abdm_callbacksPhase 2: ABHA Registration Tablesintegration.abdm_patient_profilesintegration.abdm_auth_sessionsPhase 3: HIP Linking Tablesintegration.abdm_link_requestsintegration.abdm_discovery_requestsintegration.abdm_care_contextsPhase 4: Consent and Data Exchange Tablesintegration.abdm_consentsintegration.abdm_consent_artefactsintegration.abdm_data_transfersintegration.abdm_consent_audit_logsERDAPI EndpointsPhase 1: Facility RegistrationPhase 2: ABHA Registration and PHR LoginPhase 3: HIP Linking and Care ContextsPhase 4: Consent Management and Data ExchangeWebhooks (ABDM JWT Auth, No User Session)Webhook FlowRequest Processing PipelineCallback Processing (async in Sidekiq)Handler RegistryIdempotencyState MachinesAbdmAuthSessionAbdmPatientProfile (registration_status)AbdmLinkRequestAbdmDiscoveryRequestAbdmCareContext (sync_status)AbdmConsentAbdmConsentArtefact (fetch_status)AbdmDataTransferAbdmCallbackSecurityRow-Level Security (RLS)Webhook AuthenticationPII EncryptionECDH Encryption (Fidelius)Circuit BreakersImmutable Audit LogData Erasure ComplianceFrontend IntegrationTypeScript TypesConstantsAPI Endpoint PathsAPI ClientReact Query HooksUI ComponentsABHA Registration ComponentsHIP Linking ComponentsHealth Record ComponentsConsent Management Components (Patient Detail)Real-Time EventsPatient Detail IntegrationError HandlingBackground JobsProcessCallbackJobHipDataTransferJobHiuDataFetchJobDataErasureJobMigration from LegacyStructural ChangesModel ConsolidationArchitecture ImprovementsCredential ConfigurationSee Also