If your hospital, lab, pharmacy, or clinical SaaS holds patient records that should flow into the ABDM network, you need to build an HIP (Health Information Provider) integration. The brief is straightforward — "let registered ABHA patients link their encounters with us and pull them later through any PHR app." The implementation has more sharp edges than most teams expect.
This article is the reference architecture for the M2 flow — the HIP side of ABDM. It is stack-neutral: implement in .NET, Node, Java, Go, or Python. It covers every contract you must support: discovery, care context linking via SMS deep link, Fidelius encryption on the producer side, FHIR bundle generation per NRCES profile, the visit-scoping rule that prevents over-sharing, and the dozen production gotchas that catch every first-time implementer.
For the consumer-side counterpart — M3, the HIU (Health Information User) — see our HIU reference architecture article.
Why M2 is the unsexy half of ABDM
HIU integrations get the demo glory — doctors pulling records from across India. HIP integrations get the operational reality: every patient encounter at your facility has to be turned into a linkable care context, identified by an ABHA address, packaged as a NRCES-compliant FHIR bundle, encrypted, and pushed on demand. If you build HIU first, you are a data tourist on a network with no destinations. If everyone builds HIU first (and many do), the network is empty.
Three concepts trip up teams new to HIP:
- Care contexts are not encounters — they are references to encounters. A care context (CC) is an opaque string you own (like
OP-2401orIM-21). It points to one clinical encounter in your EMR. ABDM never sees the encounter data until a downstream consent triggers a push. The CC is the only thing that lives on the network until then. - Linking is durable; data sharing is per-request. Once a patient links a CC at your facility, that link survives indefinitely. But the actual data only moves when an HIU has a granted consent and explicitly requests it. The link is a pointer; the data is on-demand.
- You are accountable for what you send. Discovery matching, visit-date scoping, FHIR profile compliance, encryption correctness — all are the HIP's responsibility. Get any of them wrong and you either fail certification, leak data, or render unusable bundles in patients' PHR apps.
The rest of this article is the architecture that handles all three correctly.
Mental model: the M2 flow has three sub-flows
Read the diagram above before writing any code. M2 looks like one flow on first reading; it is actually three interlocking sub-flows that share most plumbing.
Sub-flow A — Discovery + Link (consumer asks you about a patient): A different HIU is searching the network for patient data. The gateway broadcasts a discovery query to relevant HIPs. You match against your patient registry, return the matched ABHA address plus your unlinked care contexts. The patient receives an OTP, enters it in the requesting HIU's flow, and ABDM confirms the link.
Sub-flow B — HIP-initiated link via deep link SMS: A patient walks into your facility. Your operator captures their ABHA address at registration. After an encounter, the operator clicks "Link care contexts" in your UI, you fire /links/link/init plus /sms/notify2, the patient gets an SMS, taps the deep link to open their ABHA app, accepts in-app, and ABDM confirms the link. No OTP — the ABHA app proves identity.
Sub-flow C — Data push (after a consent has been granted): Some time later (minutes or months), an HIU has a granted consent that includes one of your linked CCs and wants the data. The HIU fires /health-information/cm/request; ABDM forwards /health-information/hip/request to you with the HIU's encryption keypair and a direct push URL. You fetch the records from your EMR, build a FHIR bundle, encrypt with the HIU's key using Fidelius, push to the HIU directly, and post status back to ABDM.
Five concepts to internalise:
- Care context (CC) — your reference to one clinical encounter. Like
OP-2401,IM-21,INV-8881. The HIP owns the namespace; ABDM never validates the format. Your CCs map 1:1 to encounters in your EMR. - Link — a durable association between an ABHA address and one or more of your UHIDs. Once linked, the patient can pull records via any HIU without re-linking.
- Discovery match — the algorithm by which you decide a patient queried via ABDM is the same person as your UHID record. Must use at least two attributes (name + DOB + gender + mobile or ABHA identifier).
- Bridge URL — the public HTTPS endpoint ABDM uses to reach your HIP callbacks. Registered once; ABDM caches it and propagates updates over 5–15 minutes.
- HI types — the eight clinical data categories: OPConsultation, Prescription, DiagnosticReport, DischargeSummary, ImmunizationRecord, WellnessRecord, HealthDocumentRecord, Invoice. Each has its own NRCES profile and FHIR slicing rules.
The four operator screens
A working HIP has at minimum four operator-facing surfaces. UI styling is yours; the logical structure below is what matters.
Screen A — Patient registration / ABHA capture
The form your front-desk operator fills when admitting a patient. Standard hospital registration fields (name, DOB, gender, mobile) plus one ABDM-specific field: ABHA address. Validation must accept either the name@cm format (3–18 chars before @, 2–32 after) or the 14-digit ABHA Number, normalised internally to the address form via the /profile/account/qrCode lookup.
After save, the operator can optionally fire ABHA card download — surface as a button that calls /profile/account/abha-card and renders the returned PDF inline. Store the captured ABHA address in your patient table against the UHID. This is the join key for every downstream M2 operation.
Screen B — Link care contexts (HIP-initiated)
The most common daily-use screen. Operator searches for a registered patient, sees a list of unlinked encounters at the facility, multi-selects which to link, submits. Each row shows: encounter date, encounter type (OPD / IPD / Lab / Pharmacy / Vaccine), display label.
Two important UX rules:
- Pre-filter by HI type per row. A lab visit can only become a DiagnosticReport CC. A vaccination row can only become an ImmunizationRecord CC. Hide invalid combinations rather than throwing validation errors after submit.
- Show already-linked CCs greyed out. Re-linking is rejected by ABDM with
ABDM-1057 Care context already linked— disable the row instead of letting the operator click through.
On submit, your backend creates one /links/link/init per CC group, sends the deep link SMS via /sms/notify2, and writes a row to your link tracking table with status PENDING_USER_CONSENT.
Screen C — Discovery requests (read-only monitor)
Real-time list of inbound discovery and link requests from any HIU on the network. Shows: timestamp, requesting HIU, ABHA address, matching status (MATCHED / NO_MATCH / DUPLICATE), action taken. Auto-refreshes every 30 seconds. This screen exists for the IT/compliance lead to verify no rogue HIU is fishing for patient data — not user-facing on the clinical floor.
Screen D — Bridge configuration and health check
Settings page for the integration admin. Fields: HIP ID (read-only after onboarding), facility IDs (one row per registered facility), bridge URL (read-only — what ABDM has on file), registered callback URLs (read-only), default Fidelius key TTL.
Add a connectivity probe button that pings ABDM's /gateway/v3/sessions, validates all six bridge URLs are reachable from the public internet, and asserts the HIP's TLS cert is valid. Should be runnable any time something looks broken.
Gateway APIs: the calls your HIP makes
Base URL: https://dev.abdm.gov.in/api/hiecm (sandbox) or the production equivalent. Headers required on every request:
Authorization: Bearer {accessToken}
REQUEST-ID: {fresh uuid}
TIMESTAMP: {ISO 8601 UTC}
X-CM-ID: sbx (or prod)
X-HIP-ID: {your registered HIP ID}
X-token: Bearer {patient link token} (only for ABHA card download and a few profile calls)
Content-Type: application/json The X-token header uses a lowercase t — this differs from the official ABDM docs and is a real gotcha. Several SDKs render the documented X-Token and get 401s.
1. Get access token
POST /gateway/v3/sessions
{
"clientId": "YOUR_CLIENT_ID",
"clientSecret": "YOUR_CLIENT_SECRET",
"grantType": "client_credentials"
} Returns { accessToken, tokenType, expiresIn }. Token typically lives 1200 seconds. Cache + refresh 30s before expiry.
2. Initiate link (HIP-initiated, deep link SMS flow)
POST /links/link/init
{
"abhaAddress": "patient@sbx",
"hipId": "YOUR_HIP_ID",
"patient": {
"id": "patient@sbx",
"referenceNumber": "UHID-12345",
"display": "Patient Display Name",
"careContexts": [
{ "referenceNumber": "OP-2401", "display": "OPD visit 2026-05-15" },
{ "referenceNumber": "IM-21", "display": "Vaccination 2026-05-10" }
],
"hiType": "OPConsultation",
"count": 1
}
} Returns 202. ABDM then calls your /links/link/on-init callback with a linkRefNumber you store against this attempt. After the patient accepts in their ABHA app via the SMS deep link, ABDM calls /links/link/on-confirm with the final linked CCs.
3. Send deep link SMS
POST /sms/notify2
{
"abhaAddress": "patient@sbx",
"hipId": "YOUR_HIP_ID",
"hipName": "Your Hospital Name",
"patientName": "Patient Display Name",
"careContexts": [{ "display": "OPD visit 2026-05-15" }, ...],
"countOfRecords": 2
} Returns 202. The ABDM-side gateway sends the SMS to the patient's registered mobile. Important: the endpoint is /sms/notify2 not /sms/notify — the old endpoint was deprecated in ABDM V3 and returns 404.
4. ABHA card download
GET /profile/account/abha-card
Headers: X-token: Bearer {linkToken} Returns image/PDF binary. Do not send an Accept header — ABDM returns 406 if present. The lowercase X-token rule applies here too.
5. Push health data (response to an HIU's request)
POST {dataPushUrl from incoming /hip/request}
{
"pageNumber": 1,
"pageCount": 1,
"transactionId": "{from incoming request}",
"entries": [
{
"content": "{base64 ciphertext}",
"media": "application/fhir+json",
"checksum": "{md5 hex of plaintext}",
"careContextReference": "OP-2401"
}
],
"keyMaterial": {
"cryptoAlg": "ECDH",
"curve": "Curve25519",
"dhPublicKey": {
"expiry": "...",
"parameters": "Curve25519/32byte random key",
"keyValue": "{base64 HIP public key}"
},
"nonce": "{base64 32-byte random nonce}"
}
} The dataPushUrl arrives in /health-information/hip/request — it points directly at the requesting HIU, not back through ABDM. After every push (or batch of pushes), POST status back to:
6. Notify push status
POST /health-information/notify
{
"notification": {
"consentId": "...",
"transactionId": "...",
"doneAt": "ISO 8601 UTC",
"notifier": { "type": "HIP", "id": "YOUR_HIP_ID" },
"statusNotification": {
"sessionStatus": "TRANSFERRED", // or FAILED, PARTIAL
"hipId": "YOUR_HIP_ID",
"statusResponses": [
{ "careContextReference": "OP-2401", "hiStatus": "OK", "description": "..." }
]
}
}
} Callbacks: the endpoints your HIP exposes
Six public HTTPS endpoints, registered with ABDM during onboarding. All must ACK with 200 OK inside five seconds.
- POST /care-contexts/discover — Gateway is asking "does this patient exist at your facility?" Body has the patient's verified identifiers (ABHA address, name, gender, year of birth, optionally mobile). You run your matching algorithm and respond async via
/care-contexts/on-discover. - POST /links/link/init — Patient (via their ABHA app or another HIP-initiated flow) wants to link a CC at your facility. Returns the list of CCs you can offer. Ack async via
/links/link/on-init. - POST /links/link/confirm — Patient submitted the OTP from your SMS (or accepted the deep link). Verify the OTP, then ack with the final linked CC list via
/links/link/on-confirm. - POST /links/link/add-contexts — HIP-initiated add-context request acknowledgement. Update your local row to LINKED.
- POST /health-information/hip/request — Critical callback. An HIU has a granted consent and wants the actual data. Body contains:
consent.id,dateRange,dataPushUrl,keyMaterial(HIU's pub key + nonce),requester(HIU info), and the list ofcareContextsthe consent covers. Ack202immediately, then async: fetch records from your EMR, build FHIR bundles, encrypt with the HIU's key, push todataPushUrl, post status to/health-information/notify. - POST /consents/hip/notify — Patient revoked a previously granted consent that includes data you have already pushed. You don't have to do anything for already-pushed data (the HIU is responsible for honouring the revoke), but log the event and decline any future requests that cite this revoked consent.
Discovery matching: the algorithm that makes or breaks adoption
Inbound /care-contexts/discover carries verified patient attributes from the gateway. You decide whether to return a match. The matching rules are partially specified by ABDM and partially your call — get them wrong and either:
- Too strict: legitimate patients can't link records — bad UX, support tickets pile up.
- Too loose: cross-patient leakage — privacy breach.
Required attributes in the match: at minimum ONE of (mobile number, abhaAddress, abhaNumber) plus name + gender + yearOfBirth. The mobile/ABHA element is the "strong" identifier; name + gender + DOB are the "weak" tie-breakers.
Recommended algorithm:
1. Normalise inbound name (lowercase, strip honorifics, collapse whitespace)
2. Filter your patient table to candidates where:
- Either mobile matches OR abhaAddress matches OR abhaNumber matches
3. Among candidates:
a. Exact name match + gender match + YOB match -> MATCHED
b. Fuzzy name (Levenshtein <= 2) + gender + YOB -> MATCHED
c. Multiple candidates -> DUPLICATE (operator reconcile)
d. Zero candidates -> NO_MATCH
4. For MATCHED:
- Build the careContexts list — restrict to encounters at YOUR facility
- Group by hiType
- Exclude already-linked CCs (return only what the patient can NEW-link)
5. Respond via /care-contexts/on-discover A real lesson from production: when ABDM's discovery passes mobile but your UHID stores landline, you'll get false NO_MATCH. Either normalise both sides to last-10-digits, or store mobile as a separate column that you always populate during registration.
Fidelius on the producer side
Same primitives as the HIU side (ECDH on Curve25519 + AES-256-GCM), but you are the encrypting party, not the decrypting party. Receive flow:
- The HIU's
keyMaterial.dhPublicKey.keyValue(base64 raw 32 bytes) arrives in your/health-information/hip/requestbody. So does the HIU'skeyMaterial.nonce. - You generate a fresh HIP ECDH keypair on Curve25519 — one per data push transaction, not one per CC.
- You generate a fresh 32-byte random HIP nonce.
- Derive shared secret:
ECDH(your_hip_private_key, hiu_public_key). - XOR HIP nonce ⊕ HIU nonce — 12 bytes of that = AES-GCM IV.
- Derive AES-256-GCM key from shared secret via HKDF-SHA256, salt = XOR'd nonce.
- For each FHIR bundle (cleartext JSON string): UTF-8 encode → AES-256-GCM encrypt → base64 the ciphertext → that's your
entries[].content. - Embed your HIP public key + nonce in
keyMaterial.dhPublicKey.keyValueandkeyMaterial.nonceof the push body so the HIU can decrypt.
The single biggest pitfall is identical to the HIU side: key serialisation. Use raw 32-byte X25519 keys, base64-encoded. If your crypto library wraps in SubjectPublicKeyInfo (DER), strip the 12-byte prefix.
Test vector you must pass before shipping: encrypt a known JSON bundle on the HIP side, decrypt with a paired HIU keypair, assert byte-equality. Cross-test against published Fidelius reference implementations (NHA publishes them in Java, JS, and Python).
FHIR bundle generation per HI type
Each HI type has a distinct NRCES profile with its own Composition.section slicing rules. Bundles that violate these are rejected by the NHA cert harness and silently fail to render in real PHR apps.
Universal scaffolding for every bundle
Every bundle includes:
Bundleresource withtype: document,timestamp,identifier,meta.profilepointing to the right NRCES bundle profileCompositionas the first entry — hastype,subject(Patient ref),author(Practitioner ref),custodian(Organization ref — your facility),section[]Patient— emit BOTH identifiers: 14-digit ABHA Number underhttps://healthid.abdm.gov.inAND the @cm-style ABHA Address underhttps://healthid.ndhm.gov.in. PHR apps render the patient banner fromPatient.identifier; emit only one and the banner shows incomplete data.Practitioner— name + MCI registration number under systemhttps://www.mciindia.orgOrganization(custodian) — your facility ID under systemhttps://facility.abdm.gov.in
Per-HI-type rules (the ones that surprise everyone)
Two profile rules trip up every first-time implementer:
- PrescriptionRecord has a closed slice that allows MedicationRequest and Binary only. DocumentReference is rejected by validators. The auto-generated prescription PDF must go in as a
Binaryresource, not wrapped in DocumentReference. - ImmunizationRecord has the opposite rule: closed slice with Immunization, ImmunizationRecommendation, and DocumentReference. Binary is not allowed here. The vaccination certificate PDF must be a DocumentReference.
Most other HI types (OPConsultation, DischargeSummary, WellnessRecord, Invoice) accept DocumentReference for the auto-generated PDF. DiagnosticReportRecord has a closed slice of at most two entries: DiagnosticReport (0..1) plus DocumentReference (0..1).
Immunization-specific gotchas
- Never emit
status: "not-done"Immunization entries without astatusReason. Violates FHIR R4 constraintimm-1. Either skip scheduled-only shots at bundle build time or fully populatestatusReason. - Vaccine date scoping: when an HIU requests data covering the last 12 months and the patient has 30 historical vaccines, do NOT return all 30. Filter by the timestamp when this particular vaccine CC was linked (your link record's
linked_at) within a tight window, since each ImmunizationRecord CC represents one shot or one batch given on one date.
Storage schema (HIP side)
The minimum HIP-side storage footprint is five tables beyond your existing EMR. SQL Server, PostgreSQL, MySQL — they all work. Schema below is portable SQL with light type aliases.
CREATE TABLE hip_patient_link (
uhid VARCHAR(64) NOT NULL,
abha_address VARCHAR(255) NOT NULL,
abha_number VARCHAR(20),
linked_at DATETIME DEFAULT CURRENT_TIMESTAMP,
source VARCHAR(32), -- M1_REGISTER, M2_DISCOVER, M2_LINK
PRIMARY KEY (uhid, abha_address)
);
CREATE TABLE care_context (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
uhid VARCHAR(64) NOT NULL,
care_context_reference VARCHAR(128) UNIQUE NOT NULL, -- your CC namespace, e.g. OP-2401
display VARCHAR(255),
hi_type VARCHAR(64), -- OPConsultation, ImmunizationRecord, ...
source_table VARCHAR(64), -- OPD_visit, vaccination_log, etc.
source_id BIGINT,
visit_date DATETIME,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE abdm_care_context (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
care_context_reference VARCHAR(128) NOT NULL,
abha_address VARCHAR(255) NOT NULL,
link_ref_number VARCHAR(64),
link_token VARCHAR(255),
status VARCHAR(32), -- PENDING / CONFIRMED / FAILED
initiated_at DATETIME,
linked_at DATETIME, -- critical for visit-date scoping
raw_payload LONGTEXT,
UNIQUE KEY uniq_cc_abha (care_context_reference, abha_address)
);
CREATE TABLE hip_keys (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
transaction_id VARCHAR(64),
private_key VARBINARY(64), -- ENCRYPTED at rest
public_key VARBINARY(64),
nonce VARBINARY(64),
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
expires_at DATETIME
);
CREATE TABLE abdm_inbox ( -- callback durability — persist, ACK, process
id BIGINT PRIMARY KEY AUTO_INCREMENT,
endpoint VARCHAR(64),
request_id VARCHAR(64),
payload_json LONGTEXT,
received_at DATETIME DEFAULT CURRENT_TIMESTAMP,
processed_at DATETIME,
status VARCHAR(32), -- RECEIVED / PROCESSED / FAILED
error_message TEXT
); linked_at in abdm_care_context is the load-bearing column. When an HIU requests data months later, the patient's date range may overlap many encounters. You must scope what you return to the visit(s) actually represented by the linked CC. A vaccination CC linked on a specific date should NOT pull in every shot the patient ever received at your facility — only the ones from that linking event. At push time, look up the linked_at for each CC in the data request, and filter your source records to within a tight window around that timestamp.
The inbox pattern is non-negotiable. ABDM's 5-second ACK window combined with heavy FHIR-build + Fidelius-encrypt work makes synchronous processing impossible at scale. Persist the callback body, ACK ABDM, then process via a worker.
State machine: the link lifecycle
Each transition writes a row to the inbox and updates the care context status. State changes are idempotent — ABDM redelivers callbacks occasionally; the second processing must be a no-op.
PENDING_USER_CONSENT→INIT_SENTon/links/link/initfiring successfullyINIT_SENT→USER_AUTHENTICATINGon/links/link/on-initarrival + SMS dispatchedUSER_AUTHENTICATING→LINKED/FAILED/EXPIREDon/links/link/on-confirmor 1-hour timeout
Twelve gotchas already paid for
Every item below is a real production failure mode that has burned implementer time.
X-tokenheader is lowercaset— ABDM docs render it asX-Tokenbut the gateway rejects mixed case with 401. Verify in your HTTP client./sms/notifyis dead — use/sms/notify2— ABDM V3 renamed the endpoint. The old one returns 404.- ABHA card download must NOT send
Acceptheader — returns 406 if present. Strip it explicitly; many HTTP clients addAccept: */*by default. - QR endpoint is camelCase —
/v3/profile/account/qrCodenot/qrcode. - Discovery is ASYNC in V3 — V2 was synchronous. V3 returns 202 immediately and you must POST to
/care-contexts/on-discoverafter running your matching algorithm. Synchronous responses are ignored. - Bridge URL updates take 5–15 minutes to propagate — PATCH the bridge config and your callbacks will continue arriving at the OLD URL for up to 15 minutes. Plan deploys around this. Keep the old URL alive during the migration window.
- Don't re-link an already-linked CC —
ABDM-1057 Care context already linked. Filter linked CCs out of the operator picker. - Vaccine scoping by linked_at — if you blanket-return every shot in the patient's history, the PHR app shows phantom data the patient didn't expect. Filter per request by the link record's
linked_at. - Never emit Immunization
status: "not-done"withoutstatusReason— FHIR validator rejects withimm-1constraint failure. - Patient resource needs BOTH identifiers — ABHA Number AND ABHA Address. PHR app banner reads
Patient.identifier; emit only one and the banner is incomplete. - TIMESTAMP clock skew > 60s = 401 — Sync NTP on every host that talks to ABDM. We have seen flaky 401s caused by 90-second drift on a VM whose time service had failed.
- dataPushUrl is HIU-direct, not gateway — ABDM hands you a URL pointing at the requesting HIU's data push endpoint. Push is peer-to-peer; the gateway only sees the status notification. If your egress firewall blocks arbitrary HIU URLs, whitelist by domain — or accept that this is part of the M2 contract.
Polling, async, and timing
ABDM events arrive over your six callbacks asynchronously; operator UIs are synchronous and impatient. Three patterns work for bridging the two:
- Long-polling — operator screen hits a backend endpoint that blocks up to 60s waiting for a state change on a specific link transaction.
- SSE (Server-Sent Events) — backend pushes state changes per transaction. Lighter than WebSocket, HTTP-only, works through corporate firewalls.
- 5-second polling — wasteful but acceptable for low-traffic operator screens. Most production HIP implementations use this.
The data push side has different timing: you typically have 60–120 seconds to complete a data push for a single transaction, but if the transaction spans many CCs or pages, partial pushes are valid and you can stream over several minutes.
Onboarding checklist
For teams starting from zero on the HIP side:
- Register your facility at the ABDM Health Facility Registry (HFR). Get a
facilityId. - Register your HIP at the ABDM bridge portal. Get sandbox
clientId+clientSecret. - Configure your bridge URL — your single public HTTPS entry point. ABDM routes all six callbacks under this base.
- Register the six callback paths under the bridge URL. ABDM pings each — all must return 200.
- Implement the four operator screens (registration, link CCs, discovery monitor, settings).
- Implement the discovery matching algorithm against your patient table.
- Implement Fidelius encryption + a round-trip test vector before you ship the data push handler.
- Implement FHIR bundle generation for the HI types your facility owns (start with OPConsultation, Prescription, ImmunizationRecord — they cover 80% of M2 transactions).
- Run a smoke-test M2 flow: register a sandbox ABHA patient, link a CC via SMS deep link, then have a paired sandbox HIU request the data and verify decryption and render in the PHR.
- Run the NHA HIP certification test suite via CodeDecodeLabs. Categories:
HIP_LINK_*,HIP_DISCOVERY_*,HIP_DATA_FLOW_*. - Apply for production credentials.
Realistic timeline: 8–12 weeks for a competent team starting from zero. Fidelius and NRCES profile validation are the schedule risks. Teams that solve both in week one finish 4–6 weeks faster.
What we have learned shipping HIPs in production
Three observations from running HIP integrations for hospitals, labs, and pharmacies on the ABDM sandbox and production network:
Discovery matching is the privacy seam. Get it too strict, your patients can't link their records and your operator floods with support tickets. Get it too loose, you leak care contexts to the wrong ABHA address. The algorithm above (strong identifier filter + name/gender/YOB validation + duplicate detection) is the minimum that survives both NHA cert and real-world data.
Visit-date scoping is the data minimisation discipline. The temptation to return everything in the patient's history when an HIU asks for "last 12 months" is real, especially for Immunization and OPConsultation. Resist it. The linked_at column on the link record is the per-CC anchor that scopes each push to the actual encounter the patient agreed to share. Sharing more is a soft privacy breach.
NRCES profile compliance is invisible work. The bundles look correct, validate green on the cert harness, but if you wrap a Prescription PDF as DocumentReference (allowed by FHIR R4 but forbidden by the NRCES Prescription profile slice), the PHR app silently shows an empty document. The renderer side has to handle both DocumentReference and Binary attachments universally — one helper function shared across all per-HI-type render paths is the cleanest pattern on both producer and consumer sides.
If you are evaluating whether to build, partner, or buy your ABDM HIP layer, we have shipped both HIP and HIU sides for hospitals, pharma networks, and PHR products since the M2 sandbox era. Our team can take you from zero to cert-ready in 8–12 weeks.
Need help architecting your HIP? Our ABDM integration services cover end-to-end M1, M2, and M3 buildouts, certification support, and production handover. We also work on broader healthcare interoperability solutions spanning FHIR, HL7, and EHR connections beyond the ABDM ecosystem. Talk to our team to scope your build.



