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
| Item | Value |
|---|---|
| DB | kulcare_dev (PostgreSQL, localhost, user simmibadhan) |
| Backend | domains/integration (namespace Integration::Abdm::*), routes at /integration/abdm/* |
| ABDM env | Sandbox (dev.abdm.gov.in) — server-side via Rails.application.credentials |
| Tenant under test | 01900000-0000-7000-8000-000000000001 |
| Facility under test | City Hospital Branch = 01900002-0000-7000-8000-000000000002 |
| HIP service_id | IN1810000370 |
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)
| Table | Purpose | Written by |
|---|---|---|
integration.abdm_facility_registrations | HIP/HIU registration per facility (facility_id, service_id, type HIP/HIU, active, service_name) | M1 facility reg |
integration.abdm_hip_registry | Auto-synced mirror of HIP registrations | DB trigger on abdm_facility_registrations |
integration.abdm_patient_profiles | Patient ↔ ABHA link (abha_address, abha_number, registration_status) | M1 profile link |
integration.abdm_auth_sessions | Multi-step OTP state machine (txn_id, status, step_data) | M1 ABHA reg / PHR login |
integration.abdm_link_requests | HIP linking flow state (auth_pending→…→linked, link_token) | M2 |
integration.abdm_care_contexts | Linked care contexts (reference_number, display, hi_type) | M2 |
integration.abdm_discovery_requests | Patient-initiated discovery | M2 (discovery) |
integration.abdm_consents | Consent requests (direction hiu/hip, purpose, hi_types, ranges) | M3 |
integration.abdm_consent_artefacts | Signed granted artefacts (incl. encryption keypair) | M3 |
integration.abdm_consent_audit_logs | Consent lifecycle audit | M3 |
integration.abdm_data_transfers | HIP→HIU encrypted data transfer records | M3 |
integration.abdm_gateway_tokens | Per-tenant cached gateway token | GatewayTokenService |
integration.abdm_callbacks | Queued inbound webhook payloads | webhooks |
integration.abdm_api_logs (+ _YYYY_MM partitions) | Every outbound ABDM call log (RLS-forced, PHI) | ApiClient |
identity.facilities | Kulcare 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
| Method | Path | Body (key params) | Writes |
|---|---|---|---|
| POST | /facility_registrations | facility_registration{facility_id, service_id, registration_type(HIP/HIU), service_name, active} + validates service_id via BridgeService against ABDM bridge-services | abdm_facility_registrations → trigger → abdm_hip_registry |
| GET | /facility_registrations | ?facility_id= | — |
| GET | /facility_registrations/:id | — | — |
| PATCH | /facility_registrations/:id | facility_registration{active,...} | abdm_facility_registrations |
| POST | /abha/registrations/send_otp | {aadhaar} (RSA-encrypted) → /v3/enrollment/request/otp | abdm_auth_sessions |
| POST | /abha/registrations/verify_otp | {session_id, otp, mobile} → /v3/enrollment/enrol/byAadhaar | abdm_auth_sessions |
| POST | /abha/registrations/suggest_address | {session_id} → /v3/enrollment/enrol/suggestion | abdm_auth_sessions |
| POST | /abha/registrations/create_address | {session_id, abha_address} → /v3/enrollment/enrol/abha-address | abdm_auth_sessions |
| POST | /abha/profiles | patient_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
| Method | Path | Body (key params) | Writes / calls |
|---|---|---|---|
| POST | /link_requests/fetch_modes | {patient_id, health_id, abha_number?, abha_address?} → search + /v3/profile/login/... modes | creates abdm_link_requests (auth_pending) |
| POST | /link_requests/auth_init | {link_request_id, auth_mode(mobile/aadhaar)} → /v3/profile/login/request/otp | abdm_link_requests |
| POST | /link_requests/auth_confirm | {link_request_id, otp} → /v3/profile/login/verify | abdm_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-signed | abdm_callbacks → ProcessCallbackJob |
Prereq for M2: the facility must have an active HIP registration (
hip_for_facility), and the patient must have a verifiedabdm_patient_profile. ✅ HIP reg now in place for City Hospital Branch.
M3 — Consent & data
| Method | Path | Body (key params) | Writes / calls |
|---|---|---|---|
| POST | /consents | {patient_id, purpose, hi_types[], date_range_from, date_range_to, data_erase_at} → /consent/v3/request/init | abdm_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 JWT | abdm_callbacks, abdm_consent_artefacts, abdm_data_transfers |
Infra
| Method | Path | Notes |
|---|---|---|
| GET | /webhooks/heartbeat | ABDM 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_id | client_id (nil) | Gateway token request |
abdm_gateway_client_secret | client_secret (nil) | Gateway token request |
abdm_cm_id | cm_id (nil) | X-CM-ID header |
abdm_gateway_host_hiecm | gateway_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 missinggrantType) - 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)— plainiso8601without precision givesABDM-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_iddomains/integration/services/abdm/gateway_token_service.rb—client_id/client_secret→:abdm_gateway_client_id/secret, session path →/api/hiecm/gateway/v3/sessions, addedgrantType: 'client_credentials'+X-CM-ID/REQUEST-ID/TIMESTAMPheaders
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_logspartitions 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 thetokenfield from login response (hastenant_id+facility_idembedded; NOT theaccess_tokenfield which has no tenant) - Result: HTTP 201, 2 rows created:
- HIP:
id=019f3fcb-4e17-71a7-bf8c-acb2ee2d7998 - HIU:
id=019f3fcb-4e36-7e7f-a2ff-29d39d014eb8
- HIP:
- 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_facilityandhiu_for_facilityboth 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, tenant01900000-...001) as HIP withservice_id = IN1810000370. - Method: Direct DB insert into
integration.abdm_facility_registrations(table was empty; bypassed API sinceIN1810000370is a production-format id not present in sandboxbridge-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_registryauto-synced (IN1810000370, HIP, active). - Scope verified:
hip_for_facility(City Hospital Branch)resolves →current_hip_registrationwill 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.