CDS Hooks on Epic and Cerner: patient-view, order-select, order-sign
A playbook for CDS Hooks against Epic and Oracle Health (Cerner) — patient-view versus order-select versus order-sign, cards versus suggestions, prefetch, and de-identified fixtures.
Ala Ben Aicha

Direct answer
patient-view, order-select and order-sign are different workflow points. A card is displayed guidance; a suggestion is an actionable FHIR change. Prefetch is data the EHR may send so the service stays fast.
Hook and card mapping
CDS Hooks is a JSON-over-HTTPS pattern: the EHR (CDS Client) POSTs to {baseUrl}/cds-services/{id} when a named workflow hook fires. Pin the CDS Hooks specification and the hook pages for patient-view, order-select and order-sign. Discovery is GET {baseUrl}/cds-services.
This is not the same article as AI in clinical workflows (model behaviour inside the EHR) or Epic vs Cerner (platform flavour). Both vendors expose CDS Hooks; the hook names and the card schema are the portable contract.
| Hook | When it fires | Required context | Typical response |
|---|---|---|---|
| patient-view | User opens a patient's chart | userId, patientId; encounterId often present |
Info / warning card, or a SMART link |
| order-select | Clinician selects one or more orders, still draft | userId, patientId, selections[], draftOrders Bundle |
Alternatives, coverage, interaction warnings while details are incomplete |
| order-sign | Clinician is about to sign; last chance before draft leaves draft | userId, patientId, full draftOrders |
Harder guidance; suggestions that create / update / delete draft resources |
Card versus suggestion versus link:
| Return | What it is | What it is not |
|---|---|---|
| Card | summary (<140 chars), indicator (info | warning | critical), source, optional detail markdown |
Not a write. An empty cards: [] is a valid 200 — “no guidance” |
| Suggestion | Labelled action set the user may accept; actions[] of FHIR create / update / delete |
Not auto-applied unless you also send systemActions and the EHR opts in. If suggestions exist, selectionBehavior is at-most-one or any |
| Link | URL, optionally type: smart to launch a SMART app |
Not a substitute for a suggestion when you need a MedicationRequest change in-place |
| Prefetch | Discovery-time FHIR read/search templates, filled by the EHR and POSTed as prefetch |
Not a guarantee. The client MAY honour zero, some, or all keys |
Prefetch tokens are {{context.patientId}} (and other first-level primitive context fields). Example discovery fragment:
{
"hook": "order-sign",
"id": "example-pgx",
"description": "Example pharmacogenomics check on sign (de-identified).",
"prefetch": {
"patient": "Patient/{{context.patientId}}",
"medications": "MedicationRequest?patient={{context.patientId}}"
}
}
If required data is in neither prefetch nor an authorized fhirServer, the service responds 412 Precondition Failed. Do not stall the EHR for seconds while you paginate the chart.
Identifier traps
| Trap | Symptom | Fix |
|---|---|---|
Treating context.patientId as a Patient URL |
Prefetch Patient/{{context.patientId}} vs Patient?identifier= confusion |
Hook context is a FHIR id, not an MRN. Map MRN in prefetch or via FHIR search |
| Using nested context as a prefetch token | {{context.draftOrders.entry.0.resource.id}} is invalid |
Only first-level primitive context fields; hooks that need an order id expose it as a top-level field |
Suggestions without selectionBehavior |
Client must treat the card as an error | Always set at-most-one or any |
Returning clinical advice as detail only |
Clinician never expands “view more” | Put the decision in summary; keep detail for evidence |
| Prefetch from another service on the same hook | Over-sharing | Clients MUST only send keys this service registered |
| Oracle Health vs Epic draft resource shapes | Suggestion update fails validation |
Fixture per EHR: same hook name, different MedicationRequest constraints. Do not assume one card JSON writes to both |
CDS Hooks is decision support plumbing. It is not a licence to give treatment, dosing, or diagnosis. Model output that changes an order still needs a clinician and, where the software is a medical device, a regulatory owner — see the MDR software guide if that question is open.
Test fixtures
De-identified. The Patient id example-p is not a person.
Discovery (truncated):
{
"services": [
{
"hook": "patient-view",
"id": "static-patient-greeter",
"title": "Static example",
"description": "Returns a static info card.",
"prefetch": {
"patientToGreet": "Patient/{{context.patientId}}"
}
}
]
}
patient-view request (truncated):
{
"hookInstance": "d1577c69-dfbe-44ad-ba6d-3e05e953b2ea",
"hook": "patient-view",
"fhirServer": "https://ehr.example.org/fhir",
"context": {
"userId": "Practitioner/example-pract",
"patientId": "example-p",
"encounterId": "example-enc"
},
"prefetch": {
"patientToGreet": {
"resourceType": "Patient",
"id": "example-p",
"gender": "female",
"birthDate": "1977-04-12",
"active": true
}
}
}
Info card (no suggestion):
{
"cards": [
{
"summary": "Example info card — not clinical advice.",
"indicator": "info",
"source": { "label": "Example CDS Service" },
"links": [
{
"label": "Open example SMART app",
"url": "https://app.example.org/launch",
"type": "smart"
}
]
}
]
}
Suggestion shape on order-sign (resource truncated, not a real medication order):
{
"cards": [
{
"summary": "Example: replace the draft order (fixture).",
"indicator": "warning",
"source": { "label": "Example CDS Service" },
"selectionBehavior": "at-most-one",
"suggestions": [
{
"label": "Apply example update",
"actions": [
{
"type": "update",
"description": "Update the draft ServiceRequest identifier only.",
"resource": {
"resourceType": "ServiceRequest",
"id": "draft-order-1",
"status": "draft",
"intent": "order",
"subject": { "reference": "Patient/example-p" }
}
}
]
}
]
}
]
}
Minimum tests:
- patient-view with prefetch present — 200, ≤1 info card, latency budget the EHR actually enforces.
- patient-view with prefetch key omitted — either 200 with a narrower card or 412; never hang.
- order-select with two
selections— card talks about the selected drafts only. - order-sign suggestion accepted vs dismissed (feedback API if the EHR sends it).
- Same service registered on Epic sandbox and an Oracle Health sandbox — compare draft MedicationRequest required fields.
Failure modes and rollback
| Failure | Detection | Rollback |
|---|---|---|
| Slow service | Chart open blocked | Time out; return cards: []; never block sign on a dead callback |
| Stale cards | order-select cards still showing at sign | Clients should drop previous cards from the same id when a new hook fires |
| Suggestion writes the wrong resource | Identifier / id mismatch on update |
Suggestions MUST send the full updated resource; disable auto-apply (systemActions) until fixtures pass |
Token leakage via fhirAuthorization |
Long-lived bearer in logs | Treat the token as transient; EHR should revoke after the hook; do not persist |
| Dual-EHR JSON assumed portable | Production 400 from one vendor | Per-EHR schema tests; shared hook names, not shared resource payloads |
Turn a dangerous service off at discovery (remove it from {baseUrl}/cds-services) rather than returning critical cards you cannot support. Rollback is: unregister the hook, keep the EHR workflow native, replay nothing — CDS Hooks is advisory unless you opted into systemActions.
Related service
If you need CDS Hooks wired into Oracle Health / Cerner (and the same cards tested against Epic), that is Cerner integration. For a scoped hook spike, use contact with project intent.
This page is not clinical advice, not a CDS Hooks certification, and not an EHR vendor partnership. Empty cards are a successful response.