Kulcare Docs

ABDM V3 — Integration & Testing Log

Owner: Simmi · Branch: feature/abdm_integration_port · API version: ABDM V3 Started: 2026-07-07

This is the living integration document for Kulcare's ABDM backend (/integration/abdm/*). It holds (a) the API reference for every Kulcare ABDM endpoint, (b) the DB tables each touches, (c) config, and (d) a chronological log of what we've registered/tested and the results.

Companions (static references):

  • Architecture/where-code-lives: docs/audits/abdm-parts-to-domains-audit-2026-07-06.md
  • Step-by-step test cases: docs/testing/abdm-v3-manual-test-plan.md
  • Importable requests: docs/testing/kulcare-abdm.postman_collection.json

1. Test environment

ItemValue
DBkulcare_dev (PostgreSQL, localhost, user simmibadhan)
Backenddomains/integration (namespace Integration::Abdm::*), routes at /integration/abdm/*
ABDM envSandbox (dev.abdm.gov.in) — server-side via Rails.application.credentials
Tenant under test01900000-0000-7000-8000-000000000001
Facility under testCity Hospital Branch = 01900002-0000-7000-8000-000000000002
HIP service_idIN1810000370

ABDM secrets (client_id, client_secret, gateway_url, cm_id, abdm.* hosts) stay server-side in Rails credentials. Postman/API testing only needs a CMS bearer token + facility_id.


2. ABDM DB tables reference (schema integration.* unless noted)

TablePurposeWritten by
integration.abdm_facility_registrationsHIP/HIU registration per facility (facility_id, service_id, type HIP/HIU, active, service_name)M1 facility reg
integration.abdm_hip_registryAuto-synced mirror of HIP registrationsDB trigger on abdm_facility_registrations
integration.abdm_patient_profilesPatient ↔ ABHA link (abha_address, abha_number, registration_status)M1 profile link
integration.abdm_auth_sessionsMulti-step OTP state machine (txn_id, status, step_data)M1 ABHA reg / PHR login
integration.abdm_link_requestsHIP linking flow state (auth_pending→…→linked, link_token)M2
integration.abdm_care_contextsLinked care contexts (reference_number, display, hi_type)M2
integration.abdm_discovery_requestsPatient-initiated discoveryM2 (discovery)
integration.abdm_consentsConsent requests (direction hiu/hip, purpose, hi_types, ranges)M3
integration.abdm_consent_artefactsSigned granted artefacts (incl. encryption keypair)M3
integration.abdm_consent_audit_logsConsent lifecycle auditM3
integration.abdm_data_transfersHIP→HIU encrypted data transfer recordsM3
integration.abdm_gateway_tokensPer-tenant cached gateway tokenGatewayTokenService
integration.abdm_callbacksQueued inbound webhook payloadswebhooks
integration.abdm_api_logs (+ _YYYY_MM partitions)Every outbound ABDM call log (RLS-forced, PHI)ApiClient
identity.facilitiesKulcare clinics (lookup facility UUID/tenant)core

Useful scopes (model): AbdmFacilityRegistration.hip_for_facility(facility_id), .hiu_for_facility(...).


3. API reference — Kulcare ABDM endpoints

All under /integration/abdm/*. Authenticated endpoints: Authorization: Bearer <cms jwt> + tenant context; facility_id passed where shown. Webhooks use ABDM JWT (no bearer).

M1 — Facility registration & ABHA

MethodPathBody (key params)Writes
POST/facility_registrationsfacility_registration{facility_id, service_id, registration_type(HIP/HIU), service_name, active} + validates service_id via BridgeService against ABDM bridge-servicesabdm_facility_registrations → trigger → abdm_hip_registry
GET/facility_registrations?facility_id=—
GET/facility_registrations/:id——
PATCH/facility_registrations/:idfacility_registration{active,...}abdm_facility_registrations
POST/abha/registrations/send_otp{aadhaar} (RSA-encrypted) → /v3/enrollment/request/otpabdm_auth_sessions
POST/abha/registrations/verify_otp{session_id, otp, mobile} → /v3/enrollment/enrol/byAadhaarabdm_auth_sessions
POST/abha/registrations/suggest_address{session_id} → /v3/enrollment/enrol/suggestionabdm_auth_sessions
POST/abha/registrations/create_address{session_id, abha_address} → /v3/enrollment/enrol/abha-addressabdm_auth_sessions
POST/abha/profilespatient_profile{patient_id, abha_address, abha_number, registration_status}abdm_patient_profiles (+ patient.abha_id)
GET/abha/profiles/:id——
POST/abha/profiles/:id/fetch_profile{x_token}—
GET/abha/profiles/:id/qr_code, /card——
POST/phr/sessions/send_otp{method, login_id, patient_id?}abdm_auth_sessions
POST/phr/sessions/verify_otp{session_id, otp}—
POST/phr/sessions/verify_user{session_id, abha_address}—
POST/phr/sessions/refresh_token{r_token}—

M2 — HIP linking ← current focus

MethodPathBody (key params)Writes / calls
POST/link_requests/fetch_modes{patient_id, health_id, abha_number?, abha_address?} → search + /v3/profile/login/... modescreates abdm_link_requests (auth_pending)
POST/link_requests/auth_init{link_request_id, auth_mode(mobile/aadhaar)} → /v3/profile/login/request/otpabdm_link_requests
POST/link_requests/auth_confirm{link_request_id, otp} → /v3/profile/login/verifyabdm_link_requests (auth_confirmed)
POST/link_requests/add_contexts{link_request_id, care_contexts:[{reference_number, display, hi_type, count}]} → /api/hiecm/hip/v3/link/carecontext (X-LINK-TOKEN)abdm_link_requests, abdm_care_contexts
GET/link_requests?patient_id=—
GET/link_requests/:id——
GET/care_contexts?patient_id=—
Webhooks/webhooks/hip/{on_token_generate,on_add_contexts,auth_notify,profile_share}, /webhooks/discovery/{discover,link_init,link_confirm}ABDM JWT-signedabdm_callbacks → ProcessCallbackJob

Prereq for M2: the facility must have an active HIP registration (hip_for_facility), and the patient must have a verified abdm_patient_profile. ✅ HIP reg now in place for City Hospital Branch.

MethodPathBody (key params)Writes / calls
POST/consents{patient_id, purpose, hi_types[], date_range_from, date_range_to, data_erase_at} → /consent/v3/request/initabdm_consents (direction=hiu)
GET/consents?patient_id=&direction=—
GET/consents/:id——
POST/cm/consents/:id/approve/deny/revoke`
GET/cm/artefacts, /cm/artefacts/:id——
GET/health_records, /health_records/:id?fetched_only=&consent_id=—
Webhooks/webhooks/hip/{consent_notify,on_request}, /webhooks/hiu/{consent_notify,on_fetch,data_notification}ABDM JWTabdm_callbacks, abdm_consent_artefacts, abdm_data_transfers

Infra

MethodPathNotes
GET/webhooks/heartbeatABDM reachability check (run first to confirm tunnel)

4. Configuration (Rails credentials) — correct keys

⚠️ The ABDM client code had wrong credential key names (fixed in port). Always use the abdm_-prefixed keys below.

Key (correct)Was (wrong)Used for
abdm_gateway_client_idclient_id (nil)Gateway token request
abdm_gateway_client_secretclient_secret (nil)Gateway token request
abdm_cm_idcm_id (nil)X-CM-ID header
abdm_gateway_host_hiecmgateway_url (nil)gateway_base_url fallback
abdm_abdm_gateway_host_hiecm—Bridge-services base
abdm_gateway_host_v3—ABDM v3 gateway base
abdm_hid_host_v3—ABHA enrollment (/v3/enrollment/...)
abdm_gateway_host_v3_modes—login OTP (/v3/profile/login/...)
abdm_gateway_host_m3—token + consent (/consent/v3/...)
abdm_health_id_number_search—M2 search by health ID
abdm_abha_address_search—M2 search by ABHA address

ABDM session endpoint (corrected)

  • URL: POST https://dev.abdm.gov.in/api/hiecm/gateway/v3/sessions
  • Path used by code: /api/hiecm/gateway/v3/sessions (was /v3/gateway/sessions — wrong)
  • Body: {"clientId":"...","clientSecret":"...","grantType":"client_credentials"} (was missing grantType)
  • Required headers: Content-Type: application/json, X-CM-ID, REQUEST-ID (UUID), TIMESTAMP (must include milliseconds, format: 2026-07-07T00:00:00.000Z; Time.current.iso8601(3) — plain iso8601 without precision gives ABDM-1016 Invalid Timestamp)

ABDM bridge-services endpoint

  • URL: GET https://dev.abdm.gov.in/api/hiecm/gateway/v3/bridge-services
  • Response shape: {"services":[{"id","name","types":["HIP","HIU","PHR"],"active":true}]}
  • IN1810000370 → {"id":"IN1810000370","name":"National_program_AS_LAKHIMPUR_Kadam_village","types":["HIP","HIU","PHR"],"active":true}

5. Testing log (chronological)

5a. Bugs found & fixed during M1 setup

Bug 1: Wrong credential keys (ApiClient + GatewayTokenService)

ApiClient and GatewayTokenService referenced credential keys that don't exist: client_id, client_secret, cm_id, gateway_url all returned nil. The correct keys are abdm_gateway_client_id, abdm_gateway_client_secret, abdm_cm_id, abdm_gateway_host_hiecm.

Fixed in:

  • domains/integration/services/abdm/api_client.rb — gateway_base_url → :abdm_gateway_host_hiecm, cm_id → :abdm_cm_id
  • domains/integration/services/abdm/gateway_token_service.rb — client_id/client_secret → :abdm_gateway_client_id/secret, session path → /api/hiecm/gateway/v3/sessions, added grantType: 'client_credentials' + X-CM-ID/REQUEST-ID/TIMESTAMP headers

Bug 2: TIMESTAMP missing milliseconds (ABDM-1016)

ApiClient used Time.current.iso8601 (no ms) for the TIMESTAMP header. ABDM rejects timestamps without milliseconds.

Fixed: Time.current.iso8601(3) (adds .fff precision).

Bug 3: Schema didn't support same-id HIP+HIU

abdm_facility_registrations had UNIQUE(service_id) — blocked registering the same service_id as both HIP and HIU. abdm_hip_registry had PRIMARY KEY (service_id) — the sync trigger would overwrite (upsert ON CONFLICT service_id, losing the first type).

Fixed: Migration db/migrate/20260706120000_support_multi_type_abdm_registrations.rb:

  • abdm_facility_registrations: UNIQUE(service_id, registration_type)
  • abdm_hip_registry: PRIMARY KEY (service_id, registration_type)
  • sync_hip_registry() trigger: upsert/delete on (service_id, registration_type)
  • Added missing abdm_api_logs partitions for Jul–Oct 2026

Bug 4: Model uniqueness not scoped to type

Model had validates :service_id, uniqueness: true — conflicts with composite unique index.

Fixed: validates :service_id, uniqueness: { scope: :registration_type }

Bug 5: BridgeService only validated HIP (not HIU)

find_hip filtered only for services with types.include?('HIP') — ignored HIU-only or multi-type services.

Fixed: Added find_service(service_id) returning the full service object with all types. find_hip now delegates to find_service and validates the HIP type.

Bug 6: Controller created only one type

Original create required registration_type from body and only created one row.

Fixed: create calls find_service, intersects ABDM's types[] with REGISTRATION_TYPES (HIP/HIU), and creates one row per applicable type in a single call.


2026-07-07 — HIP+HIU registration (M1.1) via pure API ✅

  • Action: POST /integration/abdm/facility_registrations — City Hospital Branch (01900002-...002) + IN1810000370
  • Auth: Lisa Anderson (lisa.admin@cityhospital.com) — use the token field from login response (has tenant_id + facility_id embedded; NOT the access_token field which has no tenant)
  • Result: HTTP 201, 2 rows created:
    • HIP: id=019f3fcb-4e17-71a7-bf8c-acb2ee2d7998
    • HIU: id=019f3fcb-4e36-7e7f-a2ff-29d39d014eb8
  • ABDM bridge-services response for IN1810000370: types:["HIP","HIU","PHR"] → registered HIP+HIU, skipped PHR
  • abdm_hip_registry: 2 rows synced via trigger (HIP + HIU) ✅
  • Scope verified: hip_for_facility and hiu_for_facility both resolve for City Hospital Branch ✅

Curl used:

curl -X POST http://localhost:6000/integration/abdm/facility_registrations \
  -H "Authorization: Bearer <Lisa's token (the 'token' field, not 'access_token')" \
  -H "Content-Type: application/json" \
  -d '{"facility_registration":{"facility_id":"01900002-0000-7000-8000-000000000002","service_id":"IN1810000370","service_name":"City Hospital Branch"}}'

Next: M2 HIP linkage — need a Kulcare patient at City Hospital Branch with an ABHA number to link.

  • Action: Registered City Hospital Branch (01900002-0000-7000-8000-000000000002, tenant 01900000-...001) as HIP with service_id = IN1810000370.
  • Method: Direct DB insert into integration.abdm_facility_registrations (table was empty; bypassed API since IN1810000370 is a production-format id not present in sandbox bridge-services).
  • Result: Row created id = 019f38d8-4d9a-717b-80ef-45a33b43272b, registration_type=HIP, active=true, service_name='City Hospital Branch'.
  • Trigger verified: integration.abdm_hip_registry auto-synced (IN1810000370, HIP, active).
  • Scope verified: hip_for_facility(City Hospital Branch) resolves → current_hip_registration will work for M2.
  • Next: M2 HIP linking — need a patient with a verified ABHA profile at this facility, then run fetch_modes → auth_init → auth_confirm → add_contexts.

On this page