India's Ayushman Bharat Digital Mission (ABDM) has undergone one of its most significant technical overhauls with the release of V3 APIs. If you are running a Health Information Provider (HIP), Health Information User (HIU), or Health Repository Provider (HRP) on the older v0.5 APIs, migration is no longer optional — it is mandatory. The V3 APIs are the current standard, and the official NHA ABDM Wrapper now exclusively uses V3 endpoints.
This guide is the definitive technical reference for developers migrating from ABDM v0.5 to V3. We cover every architectural change, walk through module-by-module differences with code comparisons, and provide production-tested patterns that will save you weeks of debugging.
If you are just starting your ABDM integration journey, this guide will help you build on V3 from day one and avoid the legacy patterns entirely.
Why V3? The Motivation Behind the Migration
The v0.5 APIs served as ABDM's initial production interface, but they carried architectural debt that became unsustainable as the ecosystem scaled to thousands of integrators:
- Callback complexity: Almost every v0.5 operation was asynchronous with callbacks. Even simple patient discovery required a request-callback pair through the gateway, adding latency and failure modes.
- Per-facility routing overhead: Each HIP facility needed its own bridge URL registered with ABDM. Organizations managing 50+ facilities needed 50+ tunnel endpoints.
- Inconsistent error handling: v0.5 errors were returned through callbacks, making it difficult to distinguish network failures from business logic errors.
- WSO2 gateway subscription issues: The v0.5 gateway response endpoints increasingly returned 403 errors due to WSO2 API subscription management problems — a systemic issue that NHA resolved by moving to V3.
V3 addresses all of these with three fundamental architectural changes: synchronous operations where possible, a single bridge URL with header-based routing, and a cleaner endpoint namespace under /api/hiecm.
The Big Architecture Changes
Before diving into individual modules, let us understand the three architectural shifts that affect everything in V3.
1. Synchronous Discovery (The Biggest Change)
In v0.5, patient discovery was a two-step asynchronous dance:
// v0.5 Flow (ASYNC):
// Step 1: ABDM Gateway calls your HIP
POST /v0.5/care-contexts/discover
// Step 2: Your HIP processes, then calls back
POST https://dev.abdm.gov.in/gateway/v0.5/care-contexts/on-discover
// With body: { resp: { requestId }, patient: { ... }, error: null }
In V3, discovery is synchronous. The gateway calls your HIP, and you return the result directly in the HTTP response:
// V3 Flow (SYNC):
// ABDM Gateway calls your HIP:
POST /api/v3/hip/patient/care-context/discover
// Your HIP returns the result IMMEDIATELY in the HTTP response:
{
"transactionId": "txn-uuid-here",
"patient": {
"referenceNumber": "patient-001",
"display": "Rajesh Kumar",
"careContexts": [
{
"referenceNumber": "CC-001",
"display": "OPD Visit - 10 Mar 2026"
}
],
"matchedBy": ["MOBILE"]
}
}
This is a fundamental change. Your discovery endpoint must now execute patient matching, care context lookup, and response construction within the HTTP request lifecycle. No more fire-and-forget with an async callback later.
2. Single Bridge URL with Header-Based Routing
In v0.5, if your organization operated multiple HIP facilities, you needed a separate bridge URL for each one. V3 introduces a single bridge URL that receives all callbacks, with the target facility identified by the X-HIP-ID header.
// V3: All callbacks hit your single bridge URL
// The X-HIP-ID header tells you which facility the request is for
// Example incoming request headers:
{
"X-HIP-ID": "IN2710004770", // Your facility's HIP ID
"X-CM-ID": "sbx",
"REQUEST-ID": "uuid-here",
"TIMESTAMP": "2026-03-18T10:00:00.000Z"
}
Your bridge URL router must parse X-HIP-ID from incoming requests and route to the appropriate facility handler. This is a significant architectural simplification for multi-facility deployments.
3. New API Endpoint Structure
The V3 base URL and path structure is completely different from v0.5:
// v0.5 Base URL:
https://dev.abdm.gov.in/gateway/v0.5/...
// V3 Base URL:
https://dev.abdm.gov.in/api/hiecm/...
// Production V3:
https://apis.abdm.gov.in/api/hiecm/...
Every endpoint path has changed. There is no backward compatibility — you must update every API call.
Module-by-Module Migration Guide
For a complete understanding of how ABDM milestones M1 through M4 work together, see our ABDM Integration Milestones Guide.
Authentication & Session Management
The session endpoint has moved, and the request format is slightly different:
// v0.5 Session:
POST https://dev.abdm.gov.in/gateway/v0.5/sessions
{
"clientId": "your-client-id",
"clientSecret": "your-client-secret"
}
// V3 Session:
POST https://dev.abdm.gov.in/api/hiecm/gateway/v3/sessions
{
"clientId": "your-client-id",
"clientSecret": "your-client-secret",
"grantType": "client_credentials"
}
// Response: { "accessToken": "eyJ...", "expiresIn": 600 }
Key differences:
- V3 requires the
grantTypefield (set to"client_credentials") - All subsequent API calls must include four mandatory headers:
Authorization(Bearer token),X-CM-ID(consent manager ID, typically"sbx"for sandbox),REQUEST-ID(unique UUID per request), andTIMESTAMP(ISO 8601 format) - Token expiry is typically 600 seconds (10 minutes) — implement proactive refresh
Here is a production-ready token management pattern:
// V3 Token Management (Node.js)
const V3_BASE = 'https://dev.abdm.gov.in/api/hiecm';
let accessToken = null;
let tokenExpiresAt = 0;
async function getAccessToken() {
// Refresh 60s before expiry
if (accessToken && Date.now() < tokenExpiresAt - 60000) {
return accessToken;
}
const res = await axios.post(
`${V3_BASE}/gateway/v3/sessions`,
{
clientId: process.env.ABDM_CLIENT_ID,
clientSecret: process.env.ABDM_CLIENT_SECRET,
grantType: 'client_credentials'
},
{
headers: {
'Content-Type': 'application/json',
'REQUEST-ID': uuidv4(),
'TIMESTAMP': new Date().toISOString(),
'X-CM-ID': 'sbx'
}
}
);
accessToken = res.data.accessToken;
tokenExpiresAt = Date.now() + (res.data.expiresIn || 600) * 1000;
return accessToken;
}
// Standard headers for all V3 API calls
function getV3Headers(token) {
return {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
'X-CM-ID': 'sbx',
'REQUEST-ID': uuidv4(),
'TIMESTAMP': new Date().toISOString()
};
}
Discovery & User-Initiated Linking
This module has the most dramatic change. Discovery is now synchronous in V3.
v0.5 Discovery (Async Pattern):
// v0.5: Gateway calls your HIP
// Callback endpoint: POST /v0.5/care-contexts/discover
app.post('/v0.5/care-contexts/discover', async (req, res) => {
const { requestId, transactionId, patient } = req.body;
// Acknowledge receipt
res.status(202).json({ status: 'Accepted' });
// Process asynchronously, then call back
const matchedPatient = await findPatient(patient);
await axios.post(
'https://dev.abdm.gov.in/gateway/v0.5/care-contexts/on-discover',
{
requestId: uuidv4(),
timestamp: new Date().toISOString(),
transactionId,
patient: {
referenceNumber: matchedPatient.id,
display: matchedPatient.name,
careContexts: matchedPatient.careContexts
},
resp: { requestId }
},
{ headers: authHeaders }
);
});
V3 Discovery (Synchronous Pattern):
// V3: Gateway calls your HIP, expects response in HTTP body
// Callback endpoint: POST /api/v3/hip/patient/care-context/discover
app.post('/api/v3/hip/patient/care-context/discover', (req, res) => {
const { requestId, transactionId, patient } = req.body;
const hipId = req.headers['x-hip-id'];
// Find patient in YOUR system
const matchedPatient = findPatient(
patient?.id,
patient?.name,
patient?.gender,
patient?.yearOfBirth
);
if (matchedPatient) {
// V3: Return result directly in HTTP response
return res.json({
transactionId: transactionId,
patient: {
referenceNumber: matchedPatient.id,
display: matchedPatient.name,
careContexts: matchedPatient.careContexts.map(cc => ({
referenceNumber: cc.referenceNumber,
display: cc.display
})),
matchedBy: ['MOBILE']
}
});
}
// Patient not found
res.status(404).json({
error: { code: 1000, message: 'Patient not found' }
});
});
Critical implementation notes for V3 discovery:
- The response must be returned within the HTTP timeout (typically 30 seconds). If your patient lookup involves slow database queries, optimize them.
- The
matchedByfield accepts values likeMOBILE,MR(Medical Record),HEALTH_ID, orNAME. - Error responses in V3 use an
errorobject (not anerrorsarray as in some wrapper implementations).
Link Init & Confirm remain asynchronous in V3, but the endpoint paths and response paths have changed:
// V3 Callback Paths (Gateway calls your HIP):
POST /api/v3/hip/link/care-context/init // Link initiation
POST /api/v3/hip/link/care-context/confirm // Link confirmation (OTP)
// V3 Response Paths (Your HIP calls Gateway):
POST /api/hiecm/user-initiated-linking/v3/link/care-context/on-init
POST /api/hiecm/user-initiated-linking/v3/link/care-context/on-confirm
// v0.5 equivalents (DEPRECATED):
POST /v0.5/links/link/init -> /api/v3/hip/link/care-context/init
POST /v0.5/links/link/on-init -> /user-initiated-linking/v3/link/care-context/on-init
POST /v0.5/links/link/confirm -> /api/v3/hip/link/care-context/confirm
POST /v0.5/links/link/on-confirm -> /user-initiated-linking/v3/link/care-context/on-confirm
Consent Flow
The consent notification flow in V3 follows a similar pattern to v0.5 but with updated paths and a cleaner response structure.
HIP Consent Notification (When patient grants consent):
// V3 Callback: Gateway notifies your HIP of consent
// POST /api/v3/consent/request/hip/notify
app.post('/api/v3/consent/request/hip/notify', (req, res) => {
const { requestId, notification } = req.body;
console.log('Consent notification:', notification?.consentId);
console.log('Status:', notification?.status);
// Store the consent artifact
if (notification?.consentId) {
storeConsent(notification.consentId, notification);
}
// Acknowledge receipt
res.status(202).json({ status: 'Accepted' });
// Send acknowledgement to gateway via V3 path
sendToGateway('/consent/v3/request/hip/on-notify', {
requestId: uuidv4(),
timestamp: new Date().toISOString(),
acknowledgement: [{
status: 'OK',
consentId: notification?.consentId
}],
resp: { requestId }
});
});
HIU Consent Request (When your system requests patient data):
// V3: Initiate consent request as HIU
// POST /api/hiecm/consent/v3/request/init
async function requestConsent(patientAbhaAddress, hipId) {
const token = await getAccessToken();
const body = {
requestId: uuidv4(),
timestamp: new Date().toISOString(),
consent: {
purpose: { text: 'Care Management', code: 'CAREMGT' },
patient: { id: patientAbhaAddress },
hiu: { id: YOUR_HIU_ID },
hip: { id: hipId },
requester: {
name: 'Your Organization',
identifier: {
type: 'REGNO',
value: YOUR_HIU_ID,
system: 'https://nha.gov.in'
}
},
hiTypes: ['OPConsultation', 'DiagnosticReport', 'Prescription'],
permission: {
accessMode: 'VIEW',
dateRange: {
from: '2024-01-01T00:00:00.000Z',
to: new Date().toISOString()
},
dataEraseAt: new Date(Date.now() + 365*86400000).toISOString(),
frequency: { unit: 'HOUR', value: 1, repeats: 0 }
}
}
};
return axios.post(
`${V3_BASE}/consent/v3/request/init`,
body,
{ headers: getV3Headers(token) }
);
}
Key V3 consent endpoint mapping:
// Consent V3 Response Paths (Your system calls Gateway):
/consent/v3/request/hip/on-notify // HIP acknowledges consent notification
/consent/v3/request/init // HIU initiates consent request
/consent/v3/request/status // HIU checks consent status
/consent/v3/request/hiu/on-notify // HIU acknowledges consent grant/deny
/consent/v3/fetch // HIU fetches consent artifact details
Data Transfer (HIP Side)
When a patient grants consent and the HIU requests data, ABDM asks your HIP to send health records. The V3 flow:
// V3 Callback: Gateway requests health data from your HIP
// POST /api/v3/hip/health-information/request
app.post('/api/v3/hip/health-information/request', (req, res) => {
const { requestId, transactionId, hiRequest } = req.body;
// hiRequest contains:
// - consent.id: The consent artifact ID
// - dateRange: { from, to }
// - dataPushUrl: Where to send the encrypted FHIR data
// - keyMaterial: ECDH public key for encryption
// Acknowledge
res.status(202).json({ status: 'Accepted' });
// 1. Acknowledge to gateway
sendToGateway('/data-flow/v3/health-information/hip/on-request', {
requestId: uuidv4(),
timestamp: new Date().toISOString(),
hiRequest: {
transactionId,
sessionStatus: 'ACKNOWLEDGED'
},
resp: { requestId }
});
// 2. Build FHIR bundles for each care context
const fhirBundles = buildFhirBundles(patient, consent);
// 3. Encrypt with ECDH (Curve25519) using hiRequest.keyMaterial
const encryptedEntries = fhirBundles.map(bundle => ({
content: encryptWithECDH(bundle, hiRequest.keyMaterial),
media: 'application/fhir+json',
checksum: md5(JSON.stringify(bundle)),
careContextReference: bundle.careContextRef
}));
// 4. Push encrypted data to dataPushUrl
axios.post(hiRequest.dataPushUrl, {
pageNumber: 1,
pageCount: 1,
transactionId,
entries: encryptedEntries,
keyMaterial: {
cryptoAlg: 'ECDH',
curve: 'Curve25519',
dhPublicKey: {
expiry: new Date(Date.now() + 86400000).toISOString(),
parameters: 'Curve25519/32byte random key',
keyValue: YOUR_ECDH_PUBLIC_KEY_BASE64
},
nonce: crypto.randomBytes(32).toString('base64')
}
}, { headers: getV3Headers(token) });
// 5. Notify gateway that transfer is complete
sendToGateway('/data-flow/v3/health-information/notify', {
requestId: uuidv4(),
timestamp: new Date().toISOString(),
notification: {
consentId: hiRequest.consent.id,
transactionId,
doneAt: new Date().toISOString(),
notifier: { type: 'HIP', id: YOUR_HIP_ID },
statusNotification: {
sessionStatus: 'TRANSFERRED',
hipId: YOUR_HIP_ID
}
}
});
});
Data Request (HIU Side)
As an HIU, once consent is granted, you request health information through V3:
// V3: Request health information as HIU
// POST /api/hiecm/data-flow/v3/health-information/request
async function requestHealthInfo(consentId) {
const token = await getAccessToken();
// Generate ECDH key pair for decryption
const keyPair = generateECDHKeyPair(); // Curve25519
const body = {
requestId: uuidv4(),
timestamp: new Date().toISOString(),
hiRequest: {
consent: { id: consentId },
dateRange: {
from: '2024-01-01T00:00:00.000Z',
to: new Date().toISOString()
},
dataPushUrl: `${YOUR_BRIDGE_URL}/api/v3/hiu/health-information/push`,
keyMaterial: {
cryptoAlg: 'ECDH',
curve: 'Curve25519',
dhPublicKey: {
expiry: new Date(Date.now() + 86400000).toISOString(),
parameters: 'Curve25519/32byte random key',
keyValue: keyPair.publicKeyBase64
},
nonce: crypto.randomBytes(32).toString('base64')
}
}
};
return axios.post(
`${V3_BASE}/data-flow/v3/health-information/request`,
body,
{ headers: getV3Headers(token) }
);
}
HIP-Initiated Linking (Token-Based)
V3 introduces a cleaner HIP-initiated linking flow using link tokens:
// V3 HIP-Initiated Linking Endpoints:
// Step 1: Generate link token for patient
POST /api/hiecm/v3/token/generate-token
{
"requestId": "uuid",
"abhaAddress": "patient@abdm",
"patientReference": "patient-001",
"hipId": "IN2710004770"
}
// Step 2: Add care contexts using the token
POST /api/hiecm/hip/v3/link/carecontext
{
"requestId": "uuid",
"requesterId": "IN2710004770",
"abhaAddress": "patient@abdm",
"careContexts": [
{
"referenceNumber": "CC-001",
"display": "OPD Visit - 10 Mar 2026",
"hiType": "OPConsultation"
}
]
}
// Step 3 (Optional): Send SMS deep link to patient
POST /api/hiecm/hip/v3/link/patient/links/sms/notify2
Note: In V3, the hiType field is included directly in the care context object. This is a change from v0.5 where health information types were specified separately.
Profile Share (Scan & Share)
The Scan & Share flow in V3 uses updated callback and response paths:
// V3 Callback: Patient shares profile at your facility
// POST /api/v3/hip/patient/share
app.post('/api/v3/hip/patient/share', (req, res) => {
const { requestId, intent, location, profile } = req.body;
// profile.patient contains: name, gender, yearOfBirth,
// healthId, healthIdNumber, address
// Register/update patient in your system
registerPatient(profile.patient);
res.status(202).json({ status: 'Accepted' });
// Acknowledge to gateway
sendToGateway('/patient-share/v3/on-share', {
requestId: uuidv4(),
timestamp: new Date().toISOString(),
acknowledgement: {
status: 'SUCCESS',
healthId: profile?.patient?.healthId
},
resp: { requestId }
});
});
Bridge URL Routing Architecture
One of V3's most impactful changes for multi-facility organizations is the single bridge URL pattern. Here is a production-ready routing implementation:
// Bridge URL Router for Multi-Facility V3 Integration
const express = require('express');
const app = express();
// Map HIP IDs to facility handlers
const facilityHandlers = {
'IN2710004770': require('./facilities/main-hospital'),
'IN2710004771': require('./facilities/clinic-branch-1'),
'IN2710004772': require('./facilities/clinic-branch-2'),
};
// V3 Bridge URL Middleware: Route by X-HIP-ID header
function routeByHipId(req, res, next) {
const hipId = req.headers['x-hip-id'];
if (!hipId) {
console.warn('[ROUTER] No X-HIP-ID header in request');
return res.status(400).json({
error: { code: 2000, message: 'X-HIP-ID header required' }
});
}
const handler = facilityHandlers[hipId];
if (!handler) {
console.warn(`[ROUTER] Unknown HIP ID: ${hipId}`);
return res.status(404).json({
error: { code: 2001, message: `Unknown facility: ${hipId}` }
});
}
// Attach facility handler to request context
req.facilityHandler = handler;
req.hipId = hipId;
next();
}
// Apply middleware to all V3 callback paths
app.use('/api/v3/', routeByHipId);
// Discovery routes to facility-specific handler
app.post('/api/v3/hip/patient/care-context/discover', (req, res) => {
req.facilityHandler.handleDiscovery(req, res);
});
// Link init routes to facility-specific handler
app.post('/api/v3/hip/link/care-context/init', (req, res) => {
req.facilityHandler.handleLinkInit(req, res);
});
// ... same pattern for all other V3 callback endpoints
This pattern means you register one bridge URL with ABDM (e.g., https://your-domain.com), and all callbacks for all your facilities arrive at that single endpoint. The X-HIP-ID header tells you which facility the callback is intended for.
Encryption Changes in V3
V3 uses ECDH (Elliptic Curve Diffie-Hellman) key exchange with Curve25519 for health data encryption during data transfer. The key material structure in V3:
// V3 Key Material Structure
{
"keyMaterial": {
"cryptoAlg": "ECDH",
"curve": "Curve25519",
"dhPublicKey": {
"expiry": "2026-03-19T10:00:00.000Z",
"parameters": "Curve25519/32byte random key",
"keyValue": "BASE64_ENCODED_PUBLIC_KEY"
},
"nonce": "BASE64_ENCODED_32_BYTE_RANDOM"
}
}
For production implementations, use the Fidelius library (available in Java) or implement the ECDH handshake using your language's crypto library. The encryption process:
- Generate an ephemeral Curve25519 key pair
- Perform ECDH key agreement using the requester's public key and your private key
- Derive a shared secret using HKDF
- Encrypt the FHIR bundle data using AES-GCM with the derived key
- Send your public key and nonce alongside the encrypted data
// Encryption pseudocode for V3 data transfer
const crypto = require('crypto');
function encryptForTransfer(fhirBundle, requesterKeyMaterial) {
// 1. Generate ephemeral key pair
const senderKeyPair = crypto.generateKeyPairSync('x25519');
// 2. Derive shared secret
const sharedSecret = crypto.diffieHellman({
privateKey: senderKeyPair.privateKey,
publicKey: importPublicKey(requesterKeyMaterial.dhPublicKey.keyValue)
});
// 3. Derive encryption key using HKDF
const salt = Buffer.concat([
Buffer.from(requesterKeyMaterial.nonce, 'base64'),
senderNonce
]);
const encKey = crypto.hkdfSync('sha256', sharedSecret, salt, '', 32);
// 4. Encrypt with AES-256-GCM
const iv = crypto.randomBytes(12);
const cipher = crypto.createCipheriv('aes-256-gcm', encKey, iv);
const encrypted = Buffer.concat([
cipher.update(JSON.stringify(fhirBundle)),
cipher.final()
]);
const authTag = cipher.getAuthTag();
return {
encryptedData: Buffer.concat([encrypted, authTag]).toString('base64'),
senderPublicKey: exportPublicKey(senderKeyPair.publicKey),
senderNonce: senderNonce.toString('base64')
};
}
Complete V3 Endpoint Reference
Here is the complete mapping of v0.5 to V3 endpoints for quick reference:
Session
v0.5: POST /gateway/v0.5/sessions
V3: POST /api/hiecm/gateway/v3/sessions
HIP Response Endpoints (Your HIP calls Gateway)
// User-Initiated Linking
on-discover: /user-initiated-linking/v3/patient/care-context/on-discover
on-init-link: /user-initiated-linking/v3/link/care-context/on-init
on-confirm-link: /user-initiated-linking/v3/link/care-context/on-confirm
// Consent
consent-on-notify (HIP): /consent/v3/request/hip/on-notify
consent-init (HIU): /consent/v3/request/init
consent-status: /consent/v3/request/status
consent-hiu-on-notify: /consent/v3/request/hiu/on-notify
consent-fetch: /consent/v3/fetch
// Data Flow
health-info-on-request: /data-flow/v3/health-information/hip/on-request
health-info-notify: /data-flow/v3/health-information/notify
health-info-request: /data-flow/v3/health-information/request
// HIP-Initiated Linking
generate-link-token: /v3/token/generate-token
add-care-contexts: /hip/v3/link/carecontext
link-context-notify: /hip/v3/link/context/notify
sms-notify: /hip/v3/link/patient/links/sms/notify2
// Profile Share
profile-on-share: /patient-share/v3/on-share
// Subscription
subscription-init: /subscription/v3/request/init
subscription-on-notify: /subscription/v3/request/hiu/on-notify
Gateway Callback Endpoints (Gateway calls your HIP/HIU)
// HIP Callbacks
discovery: /api/v3/hip/patient/care-context/discover
link-init: /api/v3/hip/link/care-context/init
link-confirm: /api/v3/hip/link/care-context/confirm
consent-notify: /api/v3/consent/request/hip/notify
health-info-req: /api/v3/hip/health-information/request
profile-share: /api/v3/hip/patient/share
on-generate-token: /api/v3/hip/token/on-generate-token
on-add-context: /api/v3/link/on_carecontext
context-on-notify: /api/v3/links/context/on-notify
sms-on-notify: /api/v3/patients/sms/on-notify
// HIU Callbacks
hiu-consent-notify: /api/v3/hiu/consent/request/on-notify
hiu-health-info-push: /api/v3/hiu/health-information/push
hiu-subscription: /api/v3/hiu/subscription/request/on-notify
hiu-sub-hip-notify: /api/v3/hiu/subscription/notify
Known Issues & Workarounds
Based on production testing in the ABDM sandbox (as of March 2026), here are known V3 issues and their workarounds:
1. Data Flow Endpoints Return 403
The V3 data flow endpoints (/data-flow/v3/health-information/hip/on-request and /data-flow/v3/health-information/notify) may return 403 Access Denied errors. This appears to be a WSO2 API subscription issue on the gateway side.
Workaround: Ensure your client ID has the correct API product subscriptions in the sandbox portal. If the issue persists, contact ABDM support — this is a known gateway-side issue being resolved.
2. HIP-Initiated Linking Returns 403
The /v3/token/generate-token and /hip/v3/link/carecontext endpoints may return 403 for some sandbox client IDs.
Workaround: Use a client ID that has HIP role explicitly assigned. Verify your bridge registration includes the HIP service type.
3. Profile Share Returns 403
The /patient-share/v3/on-share endpoint may return 403 for clients without the patient-share module subscription.
Workaround: Register the patient-share service in the sandbox portal bridge management section.
4. Bridge URL Update Blocked by WAF
The PATCH /gateway/v3/bridge/url API can be intermittently blocked by CloudFront WAF rules.
Workaround: Update your bridge URL through the sandbox portal UI at sandbox.abdm.gov.in instead of the API.
5. User-Initiated Linking Works Reliably
The good news: the core user-initiated linking flow (discovery, link-init, link-confirm) and consent notification work reliably in V3. The on-discover, on-init, on-confirm, and consent-on-notify gateway response endpoints consistently return 400 (validation errors for malformed test payloads) rather than 403, confirming they are properly subscribed and accessible.
Migration Checklist
Use this checklist to track your migration progress:
- Update base URL — Change from
/gateway/v0.5/to/api/hiecm/for all gateway calls - Update session endpoint — Move to
/gateway/v3/sessionsand addgrantType: "client_credentials" - Add mandatory V3 headers — Include
REQUEST-ID,TIMESTAMP,X-CM-ID, andAuthorizationon all calls - Rewrite discovery handler — Change from async callback to synchronous HTTP response pattern
- Update all callback endpoints — Register new
/api/v3/callback paths on your server - Update all gateway response paths — Replace v0.5 gateway response URLs with V3 equivalents
- Implement bridge URL routing — Add
X-HIP-IDheader parsing and facility routing middleware - Update encryption — Implement ECDH Curve25519 key exchange for data transfer
- Update care context structure — Include
hiTypedirectly in care context objects - Test in sandbox — Validate each flow end-to-end in the ABDM sandbox environment
- Update bridge URL registration — Register your single V3 bridge URL via the sandbox portal
- Run ABDM certification tests — Pass the ABDM certification process for production access
Testing Your V3 Integration
The ABDM sandbox provides a complete testing environment for V3. Here is how to validate your implementation:
1. Expose Your Server
# Use cloudflared to create a tunnel to your local server
cloudflared tunnel --url http://localhost:3200
# This gives you a public URL like:
# https://random-name.trycloudflare.com
2. Register Bridge URL
Update your bridge URL in the ABDM sandbox portal (or via API if WAF permits):
// Via API (may be WAF-blocked intermittently)
PATCH /api/hiecm/gateway/v3/bridge/url
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json
{ "url": "https://your-tunnel-url.trycloudflare.com" }
3. Test Discovery Flow
Trigger a discovery from the ABDM sandbox PHR app or use the sandbox testing tools. Your server should receive a POST to /api/v3/hip/patient/care-context/discover and return a synchronous response.
4. Validate All Callbacks
Build a debug endpoint that logs all incoming requests to verify that V3 callbacks are arriving correctly:
// Debug middleware for V3 callback validation
app.use('/api/v3/', (req, res, next) => {
console.log(`[V3-DEBUG] ${req.method} ${req.path}`);
console.log(' Headers:', JSON.stringify({
'x-hip-id': req.headers['x-hip-id'],
'x-cm-id': req.headers['x-cm-id'],
'request-id': req.headers['request-id'],
'timestamp': req.headers['timestamp']
}));
console.log(' Body:', JSON.stringify(req.body, null, 2));
next();
});
5. Verify Gateway Responses
Test that your outgoing gateway response calls succeed (return 200/202, not 403):
// Test V3 gateway response endpoints
async function testGatewayEndpoints() {
const token = await getAccessToken();
const endpoints = [
'/user-initiated-linking/v3/patient/care-context/on-discover',
'/user-initiated-linking/v3/link/care-context/on-init',
'/user-initiated-linking/v3/link/care-context/on-confirm',
'/consent/v3/request/hip/on-notify',
];
for (const path of endpoints) {
try {
const res = await axios.post(
`${V3_BASE}${path}`,
{ requestId: uuidv4(), timestamp: new Date().toISOString() },
{ headers: getV3Headers(token), validateStatus: () => true }
);
console.log(`${path}: ${res.status}`);
// 400 = good (validation error, endpoint exists)
// 403 = bad (WSO2 subscription issue)
} catch (e) {
console.error(`${path}: FAILED - ${e.message}`);
}
}
}
Frequently Asked Questions
Is ABDM v0.5 still supported?
The v0.5 APIs are technically still accessible but are in maintenance mode. NHA's official ABDM Wrapper has migrated entirely to V3, and new sandbox client registrations default to V3. Most importantly, v0.5 gateway response endpoints are experiencing increasing 403 errors due to WSO2 subscription issues that NHA is not actively fixing. The clear direction is V3.
Can I run v0.5 and V3 side by side during migration?
Yes. You can register callback handlers for both v0.5 and V3 paths simultaneously. This allows you to migrate module by module while keeping your production v0.5 integration running. Start with discovery (the biggest change), then migrate linking, consent, and data flow in order.
How do I handle the synchronous discovery timeout?
V3 synchronous discovery must return a response within the HTTP timeout window (approximately 30 seconds). If your patient matching involves external service calls or complex database queries, implement caching and optimize your lookup. Pre-index patients by ABHA address, mobile number, and name for fast matching. Return a 404 error if no match is found rather than timing out.
What happens to existing linked care contexts when I migrate?
Existing care context links established through v0.5 remain valid. The migration affects how new discovery, linking, and data transfer requests are processed. You do not need to re-link previously linked care contexts. However, consent notifications and data transfer requests for those linked contexts will now arrive through V3 callback paths.
Do I need the ABDM Wrapper, or can I integrate directly?
The NHA ABDM Wrapper is a SpringBoot+MongoDB application that abstracts the V3 API complexity. It is a good starting point if you use Java. However, for production deployments requiring custom database integration, multi-tenancy, or non-Java stacks, direct V3 API integration gives you full control. This guide provides everything you need for direct integration in any language.
Migrating to V3 or building a fresh ABDM integration? Our team has production experience with V3 APIs across all milestones — from ABHA creation through data transfer and subscription management. Talk to us →


