SMART Web Messaging, published by HL7 International - FHIR Infrastructure WG. This is not an authorized publication; it is the continuous build for version 1.0.0). This version is based on the current content of https://github.com/HL7/smart-web-messaging/ and changes regularly. See the Directory of published versions
status.*
Message Typeui.*
Message Typescratchpad.*
Message Typefhir.http
Message TypeConsiderations{::comment}
COMMON TERMS, which will reveal a hover-text definition in the IG when viewed.
NOTE: When adding an abbreviation to this list, also add the same abbreviation to the List of Abbreviations section near the end of this IG.
{:/comment} *[API]: Application Programming Interface *[CDS]: Clinical Decision Support *[CPOE]: Computerized Physician Order Entry *[CRUD]: Create Read Update Delete *[EHR]: Electronic Health Record *[EHRs]: Electronic Health Record *OAuth: An open standard for access delegation, commonly used as a way for Internet users to grant websites or applications access to their information on other websites but without giving them the passwords. *[UX]: User Experience
SMART Web Messaging enables tight UI integration between EHRs and embedded SMART apps via HTML5’s Web Messaging. SMART Web Messaging allows applications to push unsigned orders, note snippets, risk scores, or UI suggestions directly to the clinician’s EHR session. Built on the browser’s javascript window.postMessage
function, SMART Web Messaging is a simple, native API for health apps embedded within the user’s workflow.
Refer to the Introduction page for an overview, if needed.
The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this specification are to be interpreted as described in RFC2119.
SMART Web Messaging builds on HTML5’s Web Messaging specification, which
allows web pages to communicate across domains. In JavaScript, calls to
window.postMessage
can pass data between windows by dispatching
MessageEvent
objects.
A window.postMessage
-based messaging approach allows flexible,
standards-based integration that works across windows, frames and domains, and
should be readily supportable in browser controls for any EHR capable of
embedding a web application.
Request messages can originate from an app or from the EHR unless
otherwise specified. Each request message can lead to any number of response
messages, unless otherwise specified. All response messages include a
responseToMessageId
field, which correlates to the messageId
field sent in a
request message. See the following sections for more details.
For the purposes of SMART Web Messaging, a window.postMessage
call from a
caller SHALL contain a JSON message object with the following properties:
Property | Optionality | Type | Description |
---|---|---|---|
messagingHandle |
REQUIRED | string | The content of the smart_web_messaging_handle property of the OAuth access token response JSON payload. (Details below.). |
messageId |
REQUIRED | string | A unique ID for this message generated by the application. |
messageType |
REQUIRED | string | The type of this message (e.g., ui.done , scratchpad.update , status.handshake , etc). |
payload |
REQUIRED | object | The message content as specified by the messageType . See below. |
The caller SHALL pass this message object to window.postMessage
using a
valid targetOrigin
parameter.
The targetOrigin
of the EHR is provided to the app during the SMART launch
sequence in the launch context parameter smart_web_messaging_origin
. The EHR
is provided with the targetOrigin
of the app when the app is registered with
the EHR during app configuration.
Callers SHOULD NOT use "*"
for the targetOrigin
parameter for security reasons.
The receiver SHALL send a response message with the following properties:
Property | Optionality | Type | Description |
---|---|---|---|
messageId |
REQUIRED | string | A unique ID for this message generated by the caller. |
responseToMessageId |
REQUIRED | string | The messageId of the received message that this message is in response to. |
additionalResponsesExpected |
OPTIONAL | boolean | If true , indicates that the recipient can expect additional responses to arrive with the same responseToMessageId . If false or if this property is omitted, the recipient can expect that no additional responses will arrive. |
payload |
REQUIRED | object | The message content as specified by the messageType of the request message. See below. |
The response message payload
properties will vary based on the request messageType
. See message types below for details.
It is assumed that the EHR already knows the set of allowed web origins for each app, to be used in Web Messaging. After launching an app, the EHR SHOULD NOT process Web Messages originating from an origin outside this set. If the app navigates users to an origin outside of this set, it SHOULD NOT depend on Web Messages reaching the EHR.
Extensions MAY appear at any point in Request Payload or Response Payload. Extensions use the same syntax and semantics as in the FHIR Core specification. That is, an extension[]
array containing FHIR Extension elements MAY be included on any element (including extensions on primitives).
status.*
Message TypeThe purpose of the handshake is to allow apps and EHRs to determine, just after launch time, if web messaging is enabled in the other; and possibly to discover what capabilities the other supports.
The app or the EHR MAY initiate a handshake message sequence, using the
value status.handshake
as the value for the messageType
and an empty object
for the payload
. The receiver of a handshake request message SHOULD respond
exactly once with an appropriate handshake response message, and MAY provide an
OPTIONAL error
FHIR Coding parameter.
Extensions MAY be used for handshake requests and responses, and MAY be used to advertise capabilities.
An example handshake request from an app to the EHR client is presented below.
// When a SMART app is launched embedded in an iframe, window.parent and window.self
// are different objects, with window.parent being the recipient that the SMART app
// needs to reach via SMART Web Messaging.
// When a SMART app is launched standalone, window.parent and window.self are the same.
// In that case, window.opener will be the recipient that the SMART app needs to address.
const ehrWindow = window.parent !== window.self ? window.parent : window.opener;
// Read the smart_web_messaging_origin property from the launch context (alongside the access_token).
// This value provides the app with the EHR client's expected target origin.
const targetOrigin = "<smart_web_messaging_origin> from SMART launch context";
// The smart_web_messaging_handle is a launch context property also alongside the access_token.
const message = {
"messagingHandle": "<smart_web_messaging_handle> from SMART launch context",
"messageId": "<some new uid>",
"messageType": "status.handshake",
"payload": {} // See below.
};
ehrWindow.postMessage(message, targetOrigin);
The message is received in the EHR and handled as shown below.
window.addEventListener("message", function(event) {
if (event.origin !== "<the expected app origin>") {
return; // Ignore unknown origins.
}
// Verify the provided messaging handle is valid.
if (!messagingHandleIsValid(event.data.messagingHandle)) {
return; // Or handle the error some other way.
}
// Handle a status.handshake request.
if (event.data.messageType === "status.handshake") {
event.source.postMessage({
messageId: "<some new uid>",
responseToMessageId: event.data.messageId,
payload: {},
}, event.origin);
}
//...
});
ui.*
Message TypeAn embedded SMART app may improve the clinician’s user experience by attempting
to close itself when appropriate, or by requesting the EHR automatically
navigate the user to an appropriate ‘next’ activity. Messages that affect the
EHR UI will have a messageType
that matches the pattern ui.*
.
The ui
category includes messages: ui.done
and ui.launchActivity
.
The ui.done
message type signals the EHR to close the activity hosting the
SMART app.
The ui.launchActivity
message type signals the EHR to navigate the user to another
activity without closing the SMART app.
Here are some helpful, guiding principles for the intended use of launchActivity
.
launchActivity
doesn’t modify EHR data itself, but it can hint the EHR to
navigate the user to a place in the EHR workflow where the user could modify
EHR data.launchActivity
payload.launchActivity
.ui.done
and ui.launchActivity
Property | Optionality | Type | Description |
---|---|---|---|
activityType |
REQUIRED for ui.launchActivity , PROHIBITED for ui.done |
string | Navigation hint; see description below. |
activityParameters |
REQUIRED for ui.launchActivity , PROHIBITED for ui.done |
object | Navigation hint; see description below. |
The activityType
property conveys an activity type drawn from the SMART Web
Messaging Activity Catalog. In general, these activities follow the same
naming conventions as entries in the CDS Hooks catalog (noun-verb
), and will
align with CDS Hooks catalog entries where feasible. The activityType
property
conveys a navigation target such as problem-review
or order-review
, indicating
where EHR should go to after the ui message has been handled. An activity MAY
specify additional parameters that can be included in the call as additional
properties.
EHRs and Apps MAY implement activities not specified in the Activity Catalog and that all activities in the catalog are OPTIONAL.
The activityParameters
property conveys parameters specific to an activity
type. See the SMART Web Messaging Activity Catalog for details.
An example of a ui.done
message from an app to the EHR is shown below:
ehrWindow.postMessage({
"messageId": "<some new uid>",
"messagingHandle": "<smart_web_messaging_handle> from SMART launch context",
"messageType": "ui.done",
"payload": {}
}, targetOrigin);
A SMART app can use the ui.launchActivity
message type to request
navigation to a different activity without closing the app:
ehrWindow.postMessage({
"messageId": "<some new uid>",
"messagingHandle": "<smart_web_messaging_handle> from SMART launch context",
"messageType": "ui.launchActivity",
"payload": {
"activityType": "problem-review",
"activityParameters": {
// Each ui activity defines its optional and required params. See the
// Activity Catalog for more details.
"problemLocation": "Condition/123"
}
}
}, targetOrigin);
ui.done
and ui.launchActivity
Property | Optionality | Type | Description |
---|---|---|---|
status |
REQUIRED | code |
Either success or failure . Details link: LaunchStatusCode |
statusDetail |
OPTIONAL | FHIR CodeableConcept | Populated with a description of the response status code. |
The EHR SHALL respond exactly once to each ui
request message. The response payload includes a
status
parameter and an optional statusDetail
FHIR CodeableConcept:
clientAppWindow.postMessage({
"messageId": "<some new uid>",
"responseToMessageId": "<uid from the client's request>",
"payload": {
"status": "success",
"statusDetail": {
"text": "string explanation for user (optional)"
}
}
}, clientAppOrigin);
scratchpad.*
Message TypeWhile interacting with an embedded SMART app, a clinician may make decisions that should be implemented in the EHR with minimal clicks. SMART Web Messaging exposes an API to the clinician’s scratchpad within the EHR, which may contain FHIR resources unavailable on the RESTful FHIR API.
For example, the proposed CDS Hooks decision workflow can be implemented through SMART Web Messaging.
All messages affecting the scratchpad have a messageType
matching the pattern
scratchpad.*
.
SMART Web Messaging is designed to be compatible with CDS Hooks, and to implement the CDS Hooks decisions flow. For any CDS Hooks Action array, you can create a list of SMART Web Messaging API calls:
type
is used to populate the response messageType
create
→ scratchpad.create
update
→ scratchpad.update
delete
→ scratchpad.delete
resource
: used to populate the payload.resource
References to a “scratchpad” refer to an EHR capability where FHIR-structured data can be stored without the expectation of being persisted “permanently” in a FHIR server. The scratchpad can be thought of as a shared memory area, consisting of “temporary” FHIR resources that can be accessed and modified by either an app or the EHR itself. Each resource on the scratchpad has a temporary unique id (its scratchpad “location”).
A common use of the scratchpad is to hold the contents of a clinician’s “shopping cart” – i.e., data that only exist during the clinician’s session and may not have been finalized or made available through in the EHR’s FHIR API. At the end of a user’s session, selected data from the scratchpad can be persisted to the EHR’s FHIR server (e.g., a “checkout” experience, following the shopping cart metaphor).
scratchpad
OperationsScratchpad operations are conducted through an exchange of a request from an application and a response from an EHR.
The sections below detail the request and response payload requirements. For each of the response section below, the provided table includes only the most commonly used response payload fields; for full details, see the FHIR specification.
The EHR SHALL respond exactly once to each scratchpad
request message.
scratchpad.create
payload
Property | Optionality | Type | Description |
---|---|---|---|
resource |
REQUIRED | object | Conveys resource content as per the CDS Hooks Action payload.resource . |
payload
Property | Optionality | Type | Description |
---|---|---|---|
status |
REQUIRED | string | An HTTP response code (i.e. “200 OK”). |
location |
REQUIRED if a new resource has been added to the scratchapd. | string | Takes the form resourceType/id . |
outcome |
OPTIONAL | object | FHIR OperationOutcome resulting from the message action. |
The following example creates a new ServiceRequest
in the EHR’s scratchpad:
// From app -> EHR
ehrWindow.postMessage({
"messageId": "<some new uid>",
"messagingHandle": "<smart_web_messaging_handle> from SMART launch context",
"messageType": "scratchpad.create",
"payload": {
"resource": {
"resourceType": "ServiceRequest",
"status": "draft",
// additional details as needed
}
}
}, ehrOrigin);
The EHR then performs the requested operation, creating an object in an internal
scratchpad and assigning it id 123
before finally responding with:
// From EHR -> app
appWindow.postMessage({
"messageId": "<some new uid>",
"responseToMessageId": "<corresponding request messageId>",
"payload": {
"status": "201 Created",
"location": "ServiceRequest/123"
}
}, appOrigin);
scratchpad.read
The scratchpad.read
operation allows for selection of either a single resource
from the scratchpad by requesting its location
handle, or for selection of the
entire contents of the scratchpad by omitting location
from the operation
parameter list.
payload
Property | Optionality | Type | Description |
---|---|---|---|
location |
OPTIONAL | string | Takes the form resourceType/id when provided. |
payload
Property | Optionality | Type | Description |
---|---|---|---|
resource |
CONDITIONAL | object | A single Resource returned when a scratchpad.read request specifies a location . |
scratchpad |
CONDITIONAL | object | A list of Resources returned when a scratchpad.read request specifies no location , requesting all resources on the scratchpad. |
outcome |
OPTIONAL | object | FHIR OperationOutcome resulting from the message action. |
A response payload SHALL NOT populate both the resource
and scratchpad
attribute - their inclusion in the response is mutually exclusive.
The value of the scratchpad location
of any Resource accessible through the
scratchpad can be determined by concatenating the resourceType
and id
fields, delimiting them with a forward slash.
All resources returned from a scratchpad.read
operation SHALL include both
resourceType
and id
values.
This example shows how an app might request the contents of a single resource
from the scratchpad, ServiceRequest/123
.
// From app -> EHR
ehrWindow.postMessage({
"messageId": "<some new uid>",
"messagingHandle": "<smart_web_messaging_handle> from SMART launch context",
"messageType": "scratchpad.read",
"payload": {
"location": "ServiceRequest/123"
}
}, ehrOrigin);
Assuming the resource exists, the EHR could return it to the app like this:
// From EHR -> app
appWindow.postMessage({
"messageId": "<some new uid>",
"responseToMessageId": "<corresponding request messageId>",
"payload": {
"resource": {
"resourceType": "ServiceRequest",
"id": "123",
"status": "draft",
"intent": "proposal",
"subject": {
"reference": "http://example.com/Patient/123"
}
}
}
}, appOrigin);
This example shows how an app might inspect the entire contents of the scratchpad.
// From app -> EHR
ehrWindow.postMessage({
"messageId": "<some new uid>",
"messagingHandle": "<smart_web_messaging_handle> from SMART launch context",
"messageType": "scratchpad.read"
}, ehrOrigin);
The EHR could then respond with the full contents of the scratchpad in one array.
// From EHR -> app
appWindow.postMessage({
"messageId": "<some new uid>",
"responseToMessageId": "<corresponding request messageId>",
"payload": {
"scratchpad": [
{
"resourceType": "ServiceRequest",
"id": "1",
"status": "draft",
"intent": "proposal",
"subject": {
"reference": "http://example.com/Patient/123"
}
},
{
"resourceType": "MedicationRequest",
"id": "1",
"status": "draft",
"intent": "proposal",
"medicationCodeableConcept": {
"coding": [
{
"system": "http://snomed.info/sct",
"code": "108761006",
"display": "Capecitabine-containing product"
}
]
},
"subject": {
"reference": "http://example.com/Patient/123"
}
}
]
}
}, appOrigin);
Or, if the scratchpad had no entries, the EHR could respond with this:
// From EHR -> app
appWindow.postMessage({
"messageId": "<some new uid>",
"responseToMessageId": "<corresponding request messageId>"
}, appOrigin);
scratchpad.update
payload
Property | Optionality | Type | Description |
---|---|---|---|
resource |
REQUIRED | object | Conveys resource content as per CDS Hooks Action’s payload.resource . |
The resource
SHALL specify a resourceType
and an id
.
payload
Property | Optionality | Type | Description |
---|---|---|---|
status |
REQUIRED | string | An HTTP response code (i.e. “200 OK”). |
outcome |
OPTIONAL | object | FHIR OperationOutcome resulting from the message action. |
This example shows an update to a draft prescription in the context of a CDS Hooks request:
// app -> EHR: Update to a better, cheaper alternative prescription
ehrWindow.postMessage({
"messageId": "<some new uid>",
"messagingHandle": "<smart_web_messaging_handle> from SMART launch context",
"messageType": "scratchpad.update",
"payload": {
"resource": {
"resourceType": "MedicationRequest",
"id": "123",
"status": "draft"
// additional details as needed
}
}
}, ehrOrigin);
The EHR then performs the requested update, modifying an object in the scratchpad before finally responding with:
// From EHR -> app
appWindow.postMessage({
"messageId": "<some new uid>",
"responseToMessageId": "<corresponding request messageId>",
"payload": {
"status": "200 OK"
}
}, appOrigin);
scratchpad.delete
payload
Property | Optionality | Type | Description |
---|---|---|---|
location |
REQUIRED | string | Takes the form resourceType/id . |
payload
Property | Optionality | Type | Description |
---|---|---|---|
status |
REQUIRED | string | An HTTP response code (i.e. “200 OK”). |
outcome |
OPTIONAL | object | FHIR OperationOutcome resulting from the message action. |
For the app to delete MedicationRequest/456
from the EHR’s scratchpad, the app should issue this message:
// app -> EHR
ehrWindow.postMessage({
"messageId": "<some new uid>",
"messagingHandle": "<smart_web_messaging_handle> from SMART launch context",
"messageType": "scratchpad.delete",
"payload": {
"location": "MedicationRequest/456"
}
}, ehrOrigin);
Assuming the delete operation completed, the EHR could respond with:
// From EHR -> app
appWindow.postMessage({
"messageId": "<some new uid>",
"responseToMessageId": "<corresponding request messageId>",
"payload": {
"status": "200 OK"
}
}, appOrigin);
fhir.http
Message TypeIn many cases, apps will be able to establish a direct HTTPS connections to an
EHR’s FHIR server. Such connections provide a widely supported mechanism for
apps to interact with non-ephemeral state in the EHR. In some circumstances,
however, Web Messaging may be available even when direct HTTPS connections are
unavailable. In such cases, SMART Web Messaging requests with the fhir.http
message type can be used as an alternative to direct HTTPS connections.
A fhir.http
request message can invoke any FHIR interaction that the EHR
supports. The semantics are identical to those defined in the core FHIR
batch/transaction interaction. That is, a SMART Web Messaging request with the
fhir.http
type behaves like an HTTP POST to the EHR FHIR Server’s
batch/transaction endpoint. With this message type, apps can submit CRUDS
requests or other FHIR interaction requests to the EHR by assembling an
appropriate request Bundle
.
The EHR SHALL respond exactly once to each fhir.http
request message.
fhir.http
payload
Property | Optionality | Type | Description |
---|---|---|---|
bundle |
REQUIRED | object | FHIR Bundle with one or more entries that include Bundle.entry.request . Request semantics for batch and transaction bundles are as defined in the core FHIR batch/transaction interaction. |
payload
Property | Optionality | Type | Description |
---|---|---|---|
bundle |
CONDITONAL | object | REQUIRED if no outcome is supplied. FHIR Bundle with one or more entries that include Bundle.entry.response . Response semantics for batch and transaction bundles are as defined in the core FHIR batch/transaction interaction. |
outcome |
OPTIONAL | object | FHIR OperationOutcome explaining why the request could not be processed. |
This example shows a request to create a new Patient record directly in an EHR’s
FHIR server. Note that a batch request is used even though there is only a
single Patient being created; this is because batch
-type Bundles provide a
consistent and general-purpose way to describe any FHIR HTTP interaction.
// app -> EHR: Create a new patient
ehrWindow.postMessage({
"messageId": "<some new uid>",
"messagingHandle": "<smart_web_messaging_handle> from SMART launch context",
"messageType": "fhir.http",
"payload": {
"bundle": {
"resourceType": "Bundle",
"type": "batch",
"entry": [{
"request": {
"method": "POST",
"url": "Patient"
},
"resource": {
"birthDate": "1974-12-25",
"gender": "male",
"name": [{
"family": "Chalmers",
"given": ["Peter", "James"]
}]
}
}]
}
}
}, ehrOrigin);
The EHR then processes the request, storing a new patient record before finally responding with:
// From EHR -> app
appWindow.postMessage({
"messageId": "<some new uid>",
"responseToMessageId": "<corresponding request messageId>",
"payload": {
"bundle": {
"resourceType": "Bundle",
"type": "batch-response",
"entry": [{
"response": {
"status": "201 Created",
"location": "Patient/123"
}
}]
}
}
}, appOrigin);
SMART Web Messaging enables capabilities that can be authorized via
OAuth scopes, within the messaging/
category. Authorization is at the level
of message groups (e.g., messaging/ui
) rather than specific messages (e.g.,
launchActivity
). For example, a SMART app that performs dosage adjustments to
in-progress orders might request the following scopes:
patient/MedicationRequest.read
: enable access to existing prescribed medicationsmessaging/scratchpad
: enable access to draft orders (including meds) on the EHR scratchpadmessaging/ui
: enable access to EHR navigation (e.g., to signal when the app is “done”)At the time of launch, the app receives a smart_web_messaging_handle
alongside
the OAuth access_token
. This smart_web_messaging_handle
is used to
correlate window.postMessage
requests to the authorization context. We
define this as a distinct parameter from the access token itself because in many
app architectures, the access token will only live server-side, and the
smart_web_messaging_handle
is explicitly designed to be safely pushed up to
the browser environment. (It confers limited permissions, and is entirely
focused on the Web Messaging interactions without enabling full REST API
access.) A server MAY restrict the use of a single smart_web_messaging_handle
to requests from a single app window, and SHOULD apply logic to invalidate the
handle when appropriate (e.g., the server might invalidate the handle when the
user session ends).
EHR implementations MAY include additional constraints on authorization beyond these coarse-grained scopes. We encourage further experimentation in this direction, and will look to implementer experience to determine whether we can standardize more granular controls.
Note on security goals: We include a smart_web_messaging_handle
in the request to ensure that a SMART app launch has been completed prior to any SMART Web Messaging API calls. Requiring this parameter is part of a defense-in-depth strategy to mitigate some cross-site-scripting (XSS) attacks.
Location: https://ehr/authorize?
response_type=code&
client_id=app-client-id&
redirect_uri=https%3A%2F%2Fapp%2Fafter-auth&
launch=xyz123&
scope=launch+patient%2FMedicationRequest.read+messaging%2Fui+openid+profile&
state=98wrghuwuogerg97&
aud=https://ehr/fhir
Following the OAuth 2.0 handshake, the authorization server returns the
authorized SMART launch parameters alongside the access_token
. Note the
scope
, smart_web_messaging_handle
, and smart_web_messaging_origin
values:
{
"access_token": "i8hweunweunweofiwweoijewiwe",
"token_type": "bearer",
"expires_in": 3600,
"scope": "patient/Observation.read patient/Patient.read messaging/ui",
"smart_web_messaging_handle": "bws8YCbyBtCYi5mWVgUDRqX8xcjiudCo",
"smart_web_messaging_origin": "https://ehr.example.org",
"state": "98wrghuwuogerg97",
"patient": "123",
"encounter": "456"
}
The use of web messaging requires the app to be a web application, which is either embedded within an iframe or launched in a new tab/window.
SMART Web Messaging is not a context synchronization specification (see FHIRCast). Rather, it’s a collection of functions available to a web app embedded within an EHR which supports tight workflow integration.
Several design approaches were considered when first designing this specification, and some of those candidate designs are captured for posterity in the section linked below.
In the current proposal, we provide infrastructure for servers to correlate Web
Messaging requests with a specific SMART App Launch context, through the
smart_web_messaging_handle
. However, we do not require that servers make use of
this property. Refer to the historical discussion and rationale here
for background context.
The following acronyms and abbreviations are used throughout the document and are provided here for the benefit of printed-copy IG implementers and readers.
Abbreviation | Meaning |
---|---|
API | Application Programming Interface |
CDS | Clinical Decision Support |
CPOE | Computerized Physician Order Entry |
CRUD | Create Read Update Delete |
EHR | Electronic Health Record |
OAuth | An open standard for access delegation, commonly used as a way for Internet users to grant websites or applications access to their information on other websites but without giving them the passwords. |
UX | User Experience |