The Ministry of Health and Welfare, Andhra Pradesh approached us to help with implementing all the 3 milestones for Ayushman Bharat Digital Mission. Most of the ABDM APIs were built on top of FHIR, and they found out about Medblocks through our content.
Although we've had previous experience with NDHM (It was renamed in the middle of this project to ABDM), this was the first time we were implementing all 3 ABDM milestones for a major production system with millions of live patient records.
I want to share our journey here so that other implementors will have an easier time working with the ABDM APIs.
The ABDM program splits its milestones into 3 - Milestone 1, 2, and 3. We have to demonstrate the functionality at each phase to a panel of ABDM experts in order to obtain the certificate and production keys.
For a deeper understanding of what ABDM is, please check out our other article Ayushman Bharat Digital Mission: Ushering in the UPI moment of healthcare.
Milestone 1 - ABHA Creation, Verification, and Obtaining Link Token
The first milestone was to get talking with the ABHA APIs in order to verify a patient's ABHA (Ayushman Bharat Health Address) - formerly called a Health ID.
The verification process ends in the obtainment of a "link token", which is valid for 24 hours, and can be used to "link" an encounter with the patient on the ABDM network in what's called a "care context". The actual process of linking this token, however, comes under Milestone 2.
The main challenge here is that most of the APIs are based on webhooks. When you usually send a request to an API, you expect a response back with the result of the request. However, in the case of the ABDM fetch-modes, init and confirm - which are used to implement M1, your API calls will result in a 200 OK no matter what. You will receive the response on an endpoint that you pre-registered!
This makes developing and testing these APIs slightly challenging. But with tools like Pipedream and Request Inspector, this quickly became second nature. The actual challenge we faced here was that the APIs and requirements for M1 kept changing.
First, there was no official written requirement for ABHA registration as a part of M1. And that makes sense since the official ABDM website can be used by anyone to generate an ABHA. However, when we entered the first found of functional testing, we were told that generation of ABHA based on an Aadhaar card is a mandatory requirement. So, we had to go back to the drawing board.
Thanks to the project being a government initiative for the MoHW, we had quick cycles of interactive development and functional testing. We were soon ready with the other APIs as well. Most of these had to be implemented in their current EHR which was written in PHP.
After this, we had to obtain a WASA certificate (Web Application Security Audit) that declared that the application was "safe-to-host".
Due to the nature of these APIs, we foresaw the potential to exploit the exposed webhook APIs. From day one, we implemented a strict check for the requests hitting the webhooks by validating the JWTs against the public keys of the ABDM gateway. There was still the possibility of a DoS / DDoS attack since these APIs were facing the public internet. We had recommended rate limiting the incoming requests and whitelisting only the ABDM IP addresses at the L7 proxy level.
We were able to obtain the WASA certificate with ease.
Milestone 2 - Linking and exporting data
Here is where we start to actually use the link token obtained in M1 (Milestone 1). For every visit the patient makes, we create a care context using the ABDM APIs. Upon successfully creating this, the patient can see the care contexts on their Personal Health Record app (PHR)
One of the hurdles in this phase was the fact that we had to send the data out to the ABDM network after encryption as a FHIR Composition Bundle using the profiles defined by the NRCeS. Most of the players on the ABDM network were sending PDFs within the FHIR envelopes (it's still a completely valid FHIR resource), but fortunately, the MoHW were interested in sending structured data - actually meaningful FHIR resources.
To get meaningful data - especially in Condition, Observation, and Procedure resources, we had to go the extra mile and implement a proper SNOMED CT terminology setup and map their internal lab codes to LOINC. We were able to get the EHR application to use SNOMED CT by setting up a microservice that does the lookup of terms for typeahead. We also had to curate a few value sets for common conditions, procedures, and medications that showed up first in the search bar before the other results.
We then had to map the internal data structures stored in their SQL database to a FHIR compliant format. We used Liquid templates to do the mapping declaratively. It also provided us the flexibility to write the liquid templates once, and use them in multiple languages, since Liquid engines are available for most popular languages.
Next up, Encryption. There are some things in life where you just feel "WHY?". And this will remain one of those mysteries of life. There are so many different ways to send information securely from server A to server B. HTTPS is the most common and easy-to-implement way. However, for some reason, the ABDM team decided to go their own route and asked everyone to implement their own encryption algorithm from a set of instructions on a website!
I can't even begin to explain how irresponsible this is. Firstly, it's really difficult to get cryptography right, thereby the common adage "Don't roll your own crypto".
The examples given by ABDM are in Java and C#, however, we had a PHP application with the plan to migrate to NodeJS. After pouring a lot of hours into the cryptography implementation in PHP as well as NodeJs, we realized that the example implementation depends on a very specific low-level implementation from BouncyCastle (only available for Java and C#) and it was almost impossible to implement in any other language without seriously compromising the security. People are requesting examples to this day in different languages - check this thread for more.
The nonce size, prescribed by ABDM is 32 instead of the standard 24 used by standard libraries like LibSodium and NaCl which are available for all major programming languages. Looking deeper, we realized that there's an incompatibility even between the various cryptography algorithms given in the Java and C# examples!
For example, the current implementation of the PHR app sends a public key in the format of encoded Q value:
However, it expects the key format to be in the X509 when receiving data:
Some of this example code had specific snippets of code with mentions of another private company - Eka.Care, and it seems like they were responsible for some parts of the examples. The problem with the divergence in the cryptography techniques is that, it's not just hard to implement, but also breaks compatibility with other implementations using the older key formats - defeating the whole point of standardizing the data into a structured FHIR Bundle in the first place.
Moreover, the example implementations using the Bouncy castle library were not Authenticated encryption, meaning it doesn't cover securing both the person who sent it, as well as the contents of the message, and is not vetted by the community or another agency like the NIST. I think a lot of trouble could have been saved if a standard implementation of authenticated encryption with Libsodium or NaCl were used. Learn more on why Libsodium should be the de facto here.
TL;DR: ABDM should have enforced HTTPS endpoints for securing the payload, to begin with. Even in the case of manually encrypting the payload, they could have recommended encryption using vetted libraries like OpenSSL, or at least a standard authentication encryption module using libsodium/NaCl. Instead, they went with their own version of a cryptography algorithm.
This, in my opinion, is currently the weakest link in the whole ABDM ecosystem. A simple misconfiguration can cause incompatibility with other ABDM applications very easily unless everyone is using the exact same implementation.
We still had a project to finish at hand, so with a heavy heart, we simply used the examples in Java and packaged them into a Quarkus microservice that can encrypt and decrypt specifically for ABDM, with all its quirks. You can find the container on DockerHub medblocks/ndhm-crypto. Similar projects like this can also be found. This is another open-source one - https://github.com/sukreet/fidelius (thanks to the Eka.care team).
We were finally able to implement Milestone 2 APIs using the help of the Liquid templates, SNOMED CT, and Cryptography microservices. The final output of this is that the patient's records show up on the PHR app and they can consent to share this with any institute requesting it.
The next milestone was to become capable of pulling and requesting data ourselves from the network.
Milestone 3 - Sending a consent request and importing data
Here, we were supposed to receive FHIR data points from other people in the network after a consent request was sent to the patient. This was relatively simple since most of the work to map to FHIR could also be used to reverse the mapping. The cryptography microservice's decrypt endpoint could also be used for decrypting the incoming message (this decryption scheme was different from the encryption scheme).
But overall, we were able to get decent coverage of the ABDM profile's FHIR resources. We also had to cover the most common case of receiving, storing, and displaying PDFs.
Finally, we had a system in place where multiple Primary Health Care centers could create, verify ABHA addresses, link their care contexts, and send and receive data on the ABDM network.
However, we still noticed that transferring data from one center to another which was just running another version of the same EHR was still lossy in nature. This was because, with the ABDM FHIR profiles, we were only able to send a subset of the information we originally possessed in the SQL databases. We're exploring FHIR extensions to get full coverage of all the data points, but that's a fundamental limitation of FHIR which follows the 80/20 rule. A more appropriate, lossless architecture would probably be a common CDR for the clinical data.
Overall, we're happy that the MoHW, Andhra Pradesh decided to adopt ABDM in its early stages. We also respect the decision to export properly structured, semantically meaningful data with SNOMED CT codes instead of just PDFs (which is way easier to implement).
With almost more than 16 crores of ABHA addresses created, we're confident that the network will grow and prove to be an indispensable tool for health data exchange. They still have a few quirks to iron out - cryptography being the most obvious one, but I think the ABDM team, and we as a community can figure out solutions with time.
That being said, the intent of having a system in place to support sending and receiving patient records with the ease of making UPI payments is truly revolutionary. We will closely be watching the progress of the ABDM ecosystem.
“I would like to express gratitude on behalf of the Health and Welfare Department, Andhra Pradesh for the outstanding contributions made by the Medblocks team. Your passion and comprehensive understanding of every aspect of ABDM integration were remarkable. Your work on building and setting up a cryptography microservice, SNOMED CT server, mapping from local terms, and coming up with appropriate queries for drop-down lists were extremely helpful in our IT Team in integrating ABDM Services. We are grateful for your effort and contributions”
Health and Wellfare, Andhra Pradesh