01The shape of it
If you are new to ABDM, what ABDM is covers the ecosystem. This guide assumes you know what ABHA, HFR, a HIP and a HIU are, and want to know what the software actually does.
An integration talks to three kinds of ABDM endpoint:
- The ABHA service, for creating and verifying patient identities. Synchronous: you call, you get the answer.
- The registries, HFR and HPR, for facilities and professionals. Also synchronous, on their own host.
- The gateway in front of the consent manager (HIE-CM), for linking records, consent and data exchange. Asynchronous.
The gateway is asynchronous because most of what passes through it involves
someone else: the consent manager, another provider, or a patient deciding
in an app. So a call to the gateway is acknowledged immediately, usually
with 202 Accepted and no useful body, and the real answer
arrives later as an HTTP request from ABDM to you. In the
other direction, ABDM calls you when a patient looks for their records or a
HIU asks for them, and you answer by calling the gateway back.
That inversion is the defining fact of the integration. Your system is a client and a server at once, and it has to remember what it asked for.
02Environments and session tokens
NHA runs a sandbox and production, with separate credentials. Registering
software in the sandbox gives you a client id and secret. The gateway base
URLs include an /api/hiecm prefix that several published guides
leave out:
sandbox https://dev.abdm.gov.in/api/hiecm
production https://apis.abdm.gov.in/api/hiecm
Every gateway call is authorised with a session token, minted from your client credentials:
POST {base}/gateway/v3/sessions
{
"clientId": "SBX_...",
"clientSecret": "...",
"grantType": "client_credentials"
}
The token is short-lived. Cache it and reuse it until close to expiry;
do not mint one per request. This matters more than it looks, because ABDM
throttles token minting and the throttle is silent: a burst of session
requests is answered with 401 and an empty
body for several minutes. A genuinely wrong secret looks different
— a 401 with a JSON error. Code that treats every 401 as
“refresh the token and retry” turns the first into a long
outage.
03Headers on every gateway call
Authorization: Bearer <session token>
REQUEST-ID: <uuid>
TIMESTAMP: 2026-09-07T12:00:00.000Z
X-CM-ID: sbx (abdm in production)
X-HIP-ID: <facility HIP id> on HIP-role calls
X-HIU-ID: <facility HIU id> on HIU-role calls
Each has a way of going wrong:
- REQUEST-ID is a UUID you generate. It is also how ABDM de-duplicates, so a retry of the same operation should reuse the same id, not mint a new one — otherwise a timeout-and-retry can create two consent requests.
-
TIMESTAMP is UTC ISO 8601 with milliseconds and a
trailing
Z. A timestamp without the milliseconds, or with an offset instead ofZ, is rejected. -
X-CM-ID names the consent manager, and differs by
environment. Hard-coding
sbxis a classic go-live bug. - X-HIP-ID or X-HIU-ID identifies the facility the call is made for, in the role it is acting in. Send exactly one. A facility that is both HIP and HIU still sends one per call, chosen by the flow.
Header casing matters to the gateway. A few flows add their own headers,
such as X-LINK-TOKEN for HIP-initiated linking.
04The bridge URL and callbacks
The bridge URL is the public HTTPS address where ABDM delivers callbacks.
You register it once per client with PATCH
/gateway/v3/bridge/url (or in the sandbox portal, if the call is
blocked). The gateway then appends each callback’s own path to it. If
you register https://hmis.example.in/abdm, a consent
notification for a HIU arrives at
https://hmis.example.in/abdm/api/v3/hiu/consent/request/notify.
A callback endpoint has four jobs, in this order:
-
Authenticate. Callbacks carry a bearer JWT signed by
ABDM. Verify its signature and expiry against ABDM’s published
JWKS. That key set is not under the v3 prefix — in the sandbox it
is at
/gateway/v0.5/certs, and/gateway/v3/certsreturns 404. Get this wrong and every callback fails authentication while your own logs show nothing obviously misconfigured. With verification switched off instead, anyone who finds the URL can forge a consent grant. - Persist. Store the raw callback before doing anything with it. ABDM delivers at least once, so de-duplicate on the path plus the request id, and keep the record for replay and for support cases.
- Acknowledge quickly. Return promptly and do the work on a queue. A large FHIR transfer should never delay an acknowledgement.
-
Correlate. Match the callback to the operation that
caused it. Callbacks carry the original request id in
response.requestId; you need to have stored it against your own record — the patient, the link attempt, the consent request — when you made the call.
Answers to ABDM-initiated requests follow the same pattern in reverse.
When ABDM sends .../care-context/discover, you reply by
calling .../on-discover on the gateway, quoting its request
id. The on- prefix always means “this is the answer to
that”.
05ABHA calls work differently
The ABHA APIs (M1) are on their own host and are synchronous. Their
peculiarity is field-level encryption: Aadhaar numbers, OTPs and some
other identifiers must be sealed before sending, with ABDM’s RSA
public key from /v3/profile/public/certificate. The padding is
specifically RSA/ECB/OAEPWithSHA-1AndMGF1Padding — OAEP
with SHA-1 for both digests. PKCS#1 v1.5 and OAEP-SHA-256 are both
rejected. (SHA-1’s weakness is collisions, which OAEP does not rely
on; either way, the counterparty fixes the scheme.)
ABHA flows also juggle tokens beyond the session token, and confusing them is the usual cause of an unexplained 401:
- A T-token is a short-lived transaction token. A mobile OTP login that matches several ABHAs returns one; it is only good for choosing which account to continue with.
- An X-token is the citizen’s own token after a successful login, used to read their profile, card and QR.
Treat a T-token as an X-token and every profile read fails. Store neither longer than the session needs, and never store Aadhaar numbers or OTPs at all. The ABHA integration page covers the enrolment routes.
06Facility registration and bridge linkage
Before a facility can link or share anything, two things must be true on ABDM’s side. The facility must exist in HFR, which is where its HIP and HIU ids come from. And its HIP/HIU services must be registered against your bridge — the step that tells ABDM that this facility’s traffic legitimately arrives through your software.
The second step is the one teams miss. Without it, the facility is in HFR, your session token is valid, your headers are right, and linking calls are still rejected — often as a 403 or with no callback — with nothing in the error naming the cause. For a multi-tenant HMIS it has to be done per facility as each customer goes live. The HFR integration page walks through registration.
07Linking care contexts
A care context is one episode of care — an OPD visit, an admission, a lab order — that a HIP holds records for. Linking attaches it to a patient’s ABHA so it shows in their PHR app and can later be shared. There are two ways it happens.
HIP-initiated linking
The facility already has the patient’s ABHA address on file, for example from registration, and links new records as they are created:
HIP → /v3/token/generate-token abhaAddress, name, gender, yearOfBirth
← .../hip/token/on-generate-token (callback: link token)
HIP → /hip/v3/link/carecontext X-LINK-TOKEN + care contexts
← .../link/on_carecontext (callback: linked)
HIP → /hip/v3/link/context/notify new records exist
Note that the token request carries demographics alongside the ABHA address, and that two callbacks separate the start from the finish. Both have to be correlated back to the right patient and visit.
User-initiated linking
The patient, in their PHR app, searches for a facility and asks whether it holds their records. Here ABDM calls the HIP:
ABDM → .../hip/patient/care-context/discover verified identifiers, demographics
HIP → .../on-discover matching care contexts, or none
ABDM → .../hip/link/care-context/init the contexts the patient chose
HIP → .../on-init HIP sends the patient an OTP
ABDM → .../hip/link/care-context/confirm the OTP the patient entered
HIP → .../on-confirm linked
Discovery matching deserves more care than it usually gets. A verified ABHA address is a conclusive match. A mobile number shared by a family is not. When a match is ambiguous, answer that nothing was found rather than guessing: a false positive attaches one person’s records to another’s ABHA, which is the worst outcome the system can produce.
08Consent: request to artefact
A HIU that wants records starts with a consent request. It names the
patient’s ABHA address, the requester, a purpose, the health
information types wanted (OPConsultation,
Prescription, DiagnosticReport and so on), the
date range of records, and how long the data may be kept.
HIU → /consent/v3/request/init
← .../hiu/consent/request/on-init ABDM's consent request id
... the patient decides, in minutes or days ...
← .../hiu/consent/request/notify GRANTED or DENIED, artefact ids
HIU → /consent/v3/fetch per artefact
← .../hiu/consent/on-fetch the artefact's actual terms
Fetch the artefact rather than assuming it matches the request. The patient can approve a narrower scope than was asked for, and the artefact is the only statement of what was agreed. It is signed by the consent manager, and there is one per HIP involved.
On the other side, each HIP receives a consent notification at
.../consent/request/hip/notify carrying the artefact that
concerns its records, and acknowledges it with on-notify. A HIP
sees granted, revoked and expired artefacts; it never sees the original
request. Store the artefact: it is what the HIP checks when data is
requested.
09Transferring the records
With a granted artefact, the HIU asks for the data:
HIU → /data-flow/v3/health-information/request
consent artefact id, date range,
dataPushUrl, keyMaterial (public key + nonce)
← .../hiu/health-information/on-request
ABDM → HIP .../hip/health-information/request
HIP → .../health-information/hip/on-request
HIP → POST dataPushUrl encrypted FHIR bundles, HIP's key material
HIP → .../health-information/notify TRANSFERRED or FAILED
HIU → .../health-information/notify what it received
Three details shape the implementation.
-
The records bypass the gateway. The HIP posts directly
to the
dataPushUrlthe HIU advertised. The gateway only carries the messages about the transfer. The HIU’s push endpoint is necessarily unauthenticated by ABDM tokens, so make its URL unguessable per request and rate-limit it. - A request is not an authorisation. The HIP should re-check the artefact at transfer time — still granted, not revoked, not expired — and select only the care contexts whose health information types and dates fall inside it.
-
Partial success is reported per context. The session
status is
TRANSFERREDorFAILED; a transfer where one context could not be built is stillTRANSFERRED, with that context marked errored in the per-context statuses.
The HIU decrypts, validates and stores what arrives, and must honour the artefact’s data-retention date and any later revocation. The HIP and HIU pages go deeper into each side, and HIP vs HIU helps decide which you need.
10Fidelius encryption
Health data is end-to-end encrypted between HIP and HIU with a scheme ABDM calls Fidelius. Neither the gateway nor the consent manager holds a key that can open it.
- Each side generates a fresh key pair for this transfer and a random 32-byte nonce. The HIU sends its public key and nonce in the request; the HIP sends its own with the pushed data.
- Each side computes an ECDH shared secret from its own private key and the other’s public key.
- The two nonces are XORed. The first 20 bytes of the result are the salt; the last 12 bytes are the AES-GCM IV. XOR is symmetric, so both sides arrive at the same values without exchanging them.
- HKDF-SHA256 derives a 32-byte key from the shared secret and the salt.
- The FHIR bundle is sealed with AES-256-GCM, with no associated data. The tag authenticates the content: tampered or mis-keyed ciphertext fails to open rather than decrypting to garbage.
Why “Curve25519” does not mean X25519
The key material says Curve25519, and most developers reach
for their language’s X25519 function. That is the wrong function.
ABDM’s reference implementations use the Java BouncyCastle library,
which represents Curve25519 in short-Weierstrass form
— the same mathematical curve written in different coordinates.
That has two visible consequences:
-
Public keys are 65-byte uncompressed points (
0x04, then X, then Y), not X25519’s 32-byte values. - The shared secret is the Weierstrass X coordinate, which differs from the X25519 shared secret by a fixed constant from the coordinate mapping.
So an X25519 implementation derives a different AES key from the same key exchange. The insidious part is that it works perfectly against itself: your own encrypt-then-decrypt tests pass, and the failure only appears at the other party, as a GCM tag that will not verify. Test against a counterparty’s implementation or published test vectors, not just a round trip.
11FHIR bundles
What gets encrypted is a FHIR R4 document bundle
conforming to the NRCeS profiles for India. The first entry is a
Composition whose SNOMED CT type code matches the health
information type — 371530004 for an OP consultation,
440545006 for a prescription, 721981007 for a
diagnostic report, 373942005 for a discharge summary.
The failures that get bundles rejected are mostly structural rather than
clinical: references that point at no entry in the bundle, a Composition
that is missing or not first, a missing meta.profile, a
subject that resolves to nothing, or a composition code that disagrees
with the declared type. Validate before encrypting, because nobody will
tell you afterwards. The ABDM FHIR
guide and the FHIR integration page
cover the profiles in detail.
12Where integrations stall
The first successful call to ABDM is rarely where projects get stuck. These are, roughly in the order teams meet them:
- Callback correlation. The answer arrives on a different connection, sometimes minutes later, possibly twice. Without a stored request id against every outbound call, there is no way to know what it answers. Build this first, not after the demo.
- Callback authentication. The wrong JWKS URL makes every callback fail, and it looks like ABDM never called.
- Bridge linkage. A facility that is in HFR but not linked to your bridge fails in ways that do not name the cause.
- Token handling. Minting per request trips the silent 401; confusing T-tokens and X-tokens breaks ABHA profile reads.
- Consent treated as a boolean. Sending everything, or continuing after revocation, fails functional testing and is a real privacy breach in production.
- Encryption that only works against itself. See X25519 above.
-
Payload shape. Some rejections are a bare
400with an empty body, identical to malformed JSON. Where a field sits — a list versus an object,hiTypeat the group level rather than per context — matters, and version 3 moved several of them.
None of this is exotic, but together it is most of the work, and it is common to every product that integrates. That is the case for a connector: Swastik’s ABDM API handles tokens, headers, callback verification and correlation, linking, consent enforcement, FHIR assembly and Fidelius, and lets your software call ordinary REST and receive webhooks. Whichever way you build, you can try each flow in the sandbox before committing, and the milestones guide explains how the finished integration is certified.