Sidharth Ramesh
Founder and CEO
A simple alert in the EHR about a patient's allergy can be the difference between a healthy patient death.
Clinical Decision Support is one of the key reason why many organisations adopt an Electronic Health Record. It is safe to say that clinical decision support is a key pillar improving patient outcomes and avoiding fatal errors.
There are a few options to embed clinical decision support within EHR today:
In this article, we'll be looking at one such standard that makes it possible to define API based rules - The HL7 CDS Hooks Specification.
Every time a key moment happens in an EHR, like opening up a patient, or when selecting an order, a web-hook is sent to various configured HTTP endpoints. These endpoints then respond back with information about which cards to show or actions to perform.
The below illustration from the CDS Hooks website captures what this interaction look like overall.
We can define a few CDS Hooks terminologies based on what we now know:
After a CDS Service (HTTP Endpoint) is configured with a CDS Client (EHR), the CDS Client somehow needs to know all the different Hooks that the CDS Service supports. This is possible through the discovery process.
Let's assume the CDS Service is hosted on https://cds-service.com
. During the discovery process, the CDS Client will make a request to this URL:
curl 'https://cds-service.com/cds-services'
And the CDS Service is expected to respond with something looking like
{
"services": [
{
"hook": "patient-view",
"title": "Static CDS Service Example",
"description": "An example of a CDS Service that returns a static set of cards",
"id": "static-patient-greeter",
"prefetch": {
"patientToGreet": "Patient/{{context.patientId}}"
}
},
{
"hook": "order-select",
"title": "Order Echo CDS Service",
"description": "An example of a CDS Service that simply echoes the order(s) being placed",
"id": "order-echo",
"prefetch": {
"patient": "Patient/{{context.patientId}}",
"medications": "MedicationRequest?patient={{context.patientId}}"
}
}
]
}
In this response, the hook
tells the CDS Client that both the patient-view
and the order-select
hooks are supported and the EHR can call it by the id
appended to the base URL. For example, if there was a patient-view
event, in the EHR, the request go to:
curl -X POST 'https://cds-service.com/cds-services/static-patient-greeter'
Avoid this gotcha: You cannot host your cds service discovery and endpoints on just the base URL https://cds-service.com
. It always has to have a /cds-services
path after the base URL for discovery as well as service hook requests.
✅ Good: https://cds-service.com/cds-services/static-patient-greeter
❌ Bad: https://cds-service.com/static-patient-greeter
You can still host your services on another base URL like https://cds-service.com/foo
, but the CDS Service Discovery and CDS Service Hook will happen on https://cds-service.com/foo/cds-services
and https://cds-service.com/foo/cds-services/static-patient-greeter
respectively.
✅ Good: https://cds-service.com/foo/cds-services/static-patient-greeter
❌ Bad: https://cds-service.com/foo/static-patient-greeter
Notice the prefetch
property inside the static-patient-greeter
service?
{
"patientToGreet": "Patient/{{context.patientId}}"
}
This instructs the EHR to pull additional information and include it in the POST request to the CDS Service endpoint https://cds-service.com/cds-services/static-patient-greeter
.
In this case Patient/{{context.patientId}}
just represents a REST API call to the FHIR server to retrieve the Patient FHIR resource of the current patient in context. For understanding FHIR REST APIs in depth, I suggest you take a look at the official REST API guide.
The CDS Client gets this information and includes it in the request.
curl
-X POST \
-H 'Content-type: application/json' \
--data @see-below
"https://cds-service.com/cds-services/static-patient-greeter"
{
"hookInstance": "d1577c69-dfbe-44ad-ba6d-3e05e953b2ea",
"fhirServer": "http://hooks.smarthealthit.org",
"hook": "patient-view",
"fhirAuthorization": {
"access_token": "some-opaque-fhir-access-token",
"token_type": "Bearer",
"expires_in": 300,
"scope": "user/Patient.read user/Observation.read",
"subject": "cds-service4"
},
"context": {
"userId": "Practitioner/example",
"patientId": "1288992",
"encounterId": "89284"
},
"prefetch": {
"patientToGreet": {
"resourceType": "Patient",
"gender": "male",
"birthDate": "1925-12-23",
"id": "1288992",
"active": true
}
}
}
Notice the prefetch
property in the JSON above. That's what our CDS Service had requested through the discovery endpoint. You will of course, also see the fhirServer
and fhirAuthorization
properties, and yes, you will be able to authorise, and make REST API calls directly to the FHIR server using the bearer token if you want to fetch additional data. For example:
curl
-H 'Authorization: Bearer some-opaque-fhir-access-token' \
"http://hooks.smarthealthit.org/Observation?patient=1288992"
What can our CDS Service now return in response to the POST
request?
Let's assume there is nothing the CDS Service wants to show the EHR. It would just return:
{ "cards": [] }
Not very educative now, is it?
Let's look at what a real CDS Service's response might looks like with a card:
{
"cards": [
{
"summary": "Say hello to Daniel!",
"indicator": "info",
"detail": "This is a static card that greets the patient from Medblocks.",
"source": {
"label": "Medblocks CDS Service",
"url": "https://medblocks.com",
"icon": "https://cms.infra.medblocks.com/assets/2e0e885d-99dc-445f-8a89-fd910d3f7db8"
},
"links": [
{
"label": "Google",
"url": "https://google.com",
"type": "absolute"
},
{
"label": "SMART Example App",
"url": "https://smart.example.com/launch",
"type": "smart",
"appContext": "{\"session\":3456356,\"settings\":{\"module\":4235}}"
}
]
}
]
}
This would make the EHR show up a card that look something like this
Let's break this down:
cards
return all the CDS Cards that are of relevancesummary
provides a title to the card and key information can be displayed here. Here, we're asking the practitioner to greet Danielsource
provides a link in the card that attributes the card to a particular CDS Service.links
are displayed as buttons that can open up static websites or launch SMART on FHIR Apps!There's a lot more that can be done with CDS Cards. Let's explore what else is possible.
Let's take a simple example. Let's say a doctor prescribes a drug for a patient. However, a cheaper version of this same drug (generic) is available. How can we nudge the behaviour of the doctor to save this patient thousands of dollars every year?
Of course, our card can intercept the order-select
hook and display a nice little card to plead, "Please prescribe a generic and save this patient $5660 / year". But better yet, our cards can provide recommendations on what should be changed on-screen with a click of a button!
Take a look at this video below to see how this is possible:
Let's explore how this was made possible with the CDS hook response:
{
"cards": [
{
"uuid": "e99db916-9940-4143-b3eb-6e1ebef855be",
"summary": "Cost: $1274.38. Save $1218.02 with a generic medication.",
"source": {
"label": "CMS Public Use Files"
},
"indicator": "info",
"suggestions": [
{
"label": "Change to generic",
"uuid": "acac1f17-d22b-4c9d-bc08-7cd1c8d26de8",
"actions": [
{
"type": "create",
"description": "Create a resource with the newly suggested medication",
"resource": {
"resourceType": "MedicationOrder",
"id": "order-123",
"status": "draft",
"patient": {
"reference": "Patient/smart-1288992"
},
"dateWritten": "2024-07-05",
"dosageInstruction": [
{
"timing": {
"repeat": {
"frequency": 1,
"period": 1,
"periodUnits": "d"
}
},
"doseQuantity": {
"value": 1,
"system": "http://unitsofmeasure.org",
"code": "{pill}"
}
}
],
"medicationCodeableConcept": {
"text": "Ondansetron 4 MG Disintegrating Oral Tablet",
"coding": [
{
"code": "104894",
"system": "http://www.nlm.nih.gov/research/umls/rxnorm",
"display": "Ondansetron 4 MG Disintegrating Oral Tablet"
}
]
}
}
}
]
}
],
"overrideReasons": [
{
"code": "patient-requested-brand",
"system": "http://terminology.cds-hooks.org/CodeSystem/OverrideReasons",
"display": "Patient Requested Brand Product"
},
{
"code": "generic-drug-unavailable",
"system": "http://terminology.cds-hooks.org/CodeSystem/OverrideReasons",
"display": "Generic Drug Out of Stock or Unavailable"
}
]
}
]
}
Let's take a look at how this works:
suggestions
clearly indicate what needs to be changed with the actions
showing that a new MedicationOrder
resource is to be created
overrideReasons
provide two different reasons as to why a practitioner may not want to accept this card. This will show up in the feedback that we give back to the CDS Service - we'll talk about this later.And just like that, we've nudged the practitioner with a single click to save thousands of dollars for the patient! 🎉
Avoid this gotcha: The suggestions that are accepted change the state of the "scratchpad" or the draft resources on the practitioner's screen - i.e updating the medication order changes the draft prescription on-screen. It doesn't immediately change the state of the MedicationOrder resource on the FHIR Server.
✅ Your CDS Suggestions act on the practitioner's "scratchpad" or the local state of the EHR at any given time.
❌ Your CDS Suggestion does not act on the FHIR Server directly. Do not try to modify resources on the FHIR Server using the actions
property of suggestions.
There are a few other important things a CDS Response can make the CDS Client do. One of this is system actions - that allow the CDS Client to automatically apply changes to the "scratchpad" without requiring user intervention using the systemActions
property of the response.
{
"cards": [],
"systemActions": [
{
"type": "update",
"resource": {
"resourceType": "ServiceRequest",
"id": "example-MRI-59879846",
"...": "<snipped for brevity"
}
}
]
}
You can also trigger the launch of SMART Apps and external links automatically without user intervention using the autolaunchable
property of links
:
{
"cards": [
{
"uuid": "4e0a3a1e-3283-4575-ab82-028d55fe2719",
"summary": "Lung cancer screening shared decision making",
"detail": "Patient is a current smoker with a 20 pack/year history. Consider advising patient to complete lung cancer screening. The Lung Cancer Screening Shared Decision Making App (LCSSDM) has been proven to increase patient followthrough for screening.",
"source": {
"label": "Lung Cancer Screening Shared Decision Making App",
"url": "https://example.com/LCS",
"icon": "https://example.com/img/icon-100px.png"
},
"links": [
{
"label": "Github",
"url": "https://github.com",
"type": "absolute",
"autolaunchable": true
}
]
}
]
}
With great power comes great responsibility. So use these features very carefully. It's finally up to the CDS Client (EHR) whether it accepts these automatic actions or not.
The CDS Client (EHR) can send feedback to the CDS Service on what was actually done with the cards. This includes the user accepting, rejecting or ignoring the card. This feedback comes in the form of a POST request. This can include the reason for rejection as well (or not).
If a user accepts a card, this is how the feedback to the CDS Service might look like:
POST {baseUrl}/cds-services/{serviceId}/feedback
{
"feedback": [
{
"card": "4e0a3a1e-3283-4575-ab82-028d55fe2719",
"outcome": "accepted",
"acceptedSuggestions": [
{
"id": "e56e1945-20b3-4393-8503-a1a20fd73152"
}
],
"outcomeTimestamp": "2021-12-11T10:05:31Z"
}
]
}
Avoid this gotcha: The feedback is specific to each CDS Service Hook. In the case of our patient greeter service on https://cds-service.com/cds-services/static-patient-greeter
, the feedback on card acceptance / rejection will go to https://cds-service.com/cds-services/static-patient-greeter/feedback
.
✅ Good: Implement a feedback endpoint for each service: https://cds-service.com/cds-services/static-patient-greeter/feedback
or https://cds-service.com/cds-services/*/feedback
.
❌ Bad: Implement a feedback route on the base URL directly: https://cds-service.com/cds-services/feedback
CDS Hooks transmits sensitive patient data outside of the EHR to a third party. Here are some important security considerations:
Beware of Alert Fatigue: CDS Hooks provide a great way to nudge the behaviour of doctors, it's important to understand Alert Fatigue and it's consequences. Provide alerts and cards on things that are crucial and can provide immediate value. Understand that the practitioner might see hundreds of alerts a day, and try to provide alerts that practitioners can trust and will look forward to seeing.
✅ Good: Provide high-value alerts that practitioners look forward to acting on.
❌ Bad: Spam a practitioner with trivial alerts just because you can.
So you've gotten the basics of how CDS Hooks works if you've made it this far. You may be tempted to checkout Epic's CDS Hooks documentation if you want to implement CDS Hooks today with a real-world EHR. I'd recommend that you try and tackle the original CDS Hooks specification first now that you have a better understanding of what the terminology means and how things work!
If you want to build your own CDS Hook Service with the sandbox, follow along with the video below.
CDS Hooks are a specification by HL7 that enables EHRs to integrate Clinical Decision Support from 3rd parties using RESTful HTTP APIs
CDS Hooks is based on RESTful HTTP APIs. It utilizes the HL7 FHIR to model healthcare data and integrates well with the SMART on FHIR authorization framework to launch external applications as a link on a CDS Card.
By implementing CDS Hooks, Healthcare Systems obtain the ability to delegate the Clinical Decision Support logic to 3rd parties. This can be a powerful way to ensure that clinicians always receive the latest and most relevant alerts in their EHR, without having to constantly upgrade the EHR system. CDS Hooks can also integrate the provider's workflow with payers and enable point-of-care quality measures and pre-authorization.
Some use cases of CDS Hooks in the clinical setting are to display Drug-Drug or Drug-Allergy Interaction alerts, Insurance Pre-Authorization alerts, Nudges to improve Quality Measures and Reduce the cost of services.
CDS Hooks development can be simple and straightforward if you know how to build a JSON REST Server. Watch our step-by-step guide at How To Create A FHIR CDS Hook Service
CDS Hooks are a relatively recent specification and have limited EHR support. The specification currently only has a few mature hooks like - patient-view, order-select, and order-sign. The specification also leaves a lot of the implementation details up to the CDS Client which might mean CDS Services might have to develop slightly different versions of their software for each EHR.
Unlock the secrets to mastering FHIR in our exclusive, free live webinar. Discover why now is the prime time to excel and how you can become an expert.
We use cookies to understand how you use our site and improve your experience. By accepting our use of cookies, you consent to the use of cookies in accordance with our privacy policy.