This page is part of the SMART Web Messaging Implementation Guide: STU1 (v0.1.0: STU 1 Ballot 1) based on FHIR R4. The current version which supercedes this version is 1.0.0. For a full list of available versions, see the Directory of published versions
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.
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.
Clinical workflow systems (such as EHRs) can launch SMART applications in many ways: automatically at specific points in the workflow, by user interaction in the UI, or in response to a suggestion from a CDS Hooks Service - just to name a few. Once launched, web applications are often embedded within an iframe of the main UI. In this model, the new application appears in close proximity to a patient’s chart and can work with the EHR via RESTful FHIR API. These RESTful APIs are great for CRUD operations on a database, but they don’t enable tight workflow integration or access to draft FHIR resources that may only exist in memory on the EHR client.
For these embedded apps, there are some key use cases that SMART and CDS Hooks don’t address today:
Additionally, SMART Web Messaging enables other interesting capabilities. For example:
SMART Web Messaging builds on HTML5’s Web Messaging specification, which
allows web pages to communicate across domains. In JavaScript, calls to
window.postMessage
pass MessageEvent
objects between windows.
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 either thin or
thick-client EHRs.
For the purposes of SMART Web Messaging, a window.postMessage
call from an app to an EHR client 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 | ID | A unique ID for this message generated by the application. |
messageType |
REQUIRED | string | The type of this message (e.g., ui.done , scratchpad.update ). |
payload |
REQUIRED | object | The message content as specified by the messageType . See below. |
This message object must be passed to window.postMessage
using a valid targetOrigin
parameter. The EHR provides this value to the app in the initial SMART launch context, in the smart_messaging_origin
property from the OAuth token. Applications SHOULD refrain from using "*"
for the targetOrigin
parameter for security reasons.
An example call 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 of MessageEvents.
// When a SMART app is launched standalone, window.parent and window.self are the same.
// In that case, window.opener will be the object receiving MessageEvents.
const targetWindow = window.parent !== window.self ? window.parent : window.opener;
// Read the smart_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_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 guid>",
"messageType": "scratchpad.create",
"payload": {} // See below.
};
targetWindow.postMessage(message, targetOrigin);
In the EHR, the message is received and is handled as shown below.
window.addEventListener("message", function(event) {
if (event.origin !== "<the app's expected origin>") {
return; // Ignore unknown origins.
}
//
// TODO: Handle the message here by using the contents of event.data.
//
// Send a response back to the app.
const response = {
... // See below for more details on the response properties.
};
event.source.postMessage(response, event.origin);
});
The EHR SHALL send a response message with the following properties:
Property | Optionality | Type | Description |
---|---|---|---|
messageId |
REQUIRED | ID | A unique ID for this message generated by the application. |
responseToMessageId |
REQUIRED | ID | The messageId of the received message that this message is in response to. |
payload |
REQUIRED | object | The message content as specified by the messageType of the request message. See below. |
In addition to sending Web Messaging Responses to the app, the EHR MAY also send “unsolicited” requests to the app, omitting a responseToMessageId
property. The semantics of such requests are are implementation-specific.
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 proper targetOrigin
to use in its
call to window.postMessage
because it has demonstrated that it can SMART
launch the app at a known SMART launch URL. The SMART launch URL SHALL be
prefixed with the proper value to use for the app targetOrigin
, and the launch
URL SHALL not redirect users to a different origin after launch.
In a more detailed example response, the EHR may send one return message like:
window.addEventListener("message", function(event) {
if (event.origin != "<the app's expected origin>") {
return; // Ignore unknown origins.
}
//
// TODO: Handle the message here, using the contents of event.data.
//
// Send a response back to the app.
const response = {
"responseToMessageId": event.data.messageId,
"messageId": "<some new guid>",
// The response payload is modeled after Bundle.entry.response.
// See: https://www.hl7.org/fhir/bundle-definitions.html#Bundle.entry.response
"payload": {
"location": "Example/123",
// For errors encountered handling the request, the EHR can add an
// OperationOutcome to contain additional information.
// See: https://www.hl7.org/fhir/operationoutcome.html
"outcome": {},
"status": "200 OK",
}
};
event.source.postMessage(response, event.origin);
});
Then, back in the app, the message response is received and handled as shown in this example.
window.addEventListener("message", function(event) {
if (event.origin != "<the app's origin>") {
return; // Ignore unknown origins.
}
const requestId = event.data.responseToMessageId;
if (event.data.payload.status == "200 OK") {
// Success!
} else {
// Error handling.
}
});
This mechanism enables a full request/response pattern.
Applications SHOULD be prepared to see, at most, one incoming message with a
given responseToMessageId
. If multiple response messages (e.g., streams) are
needed, this can be accomplished by having the server send “unsolicited”
messages, i.e., messages with no responseToMessageId
, after a client’s initial
request.
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, and optionally navigates the user to a ‘next’ activity.
The ui.launchActivity
message type signals the EHR to navigate the user to another
activity without closing the SMART app.
ui.done
and ui.launchActivity
Property | Optionality | Type | Description |
---|---|---|---|
activityType |
CONDITIONAL | string | REQUIRED for ui.launchActivity , optional for ui.done . Navigation hint; see description below. |
activityParameters |
CONDITIONAL | object | REQUIRED for ui.launchActivity , optional for ui.done . Navigation hint; see description below. |
ui.done
and ui.launchActivity
Property | Optionality | Type | Description |
---|---|---|---|
success |
REQUIRED | boolean | true if the request has been accepted; false if it has been rejected. |
details |
OPTIONAL | string | A human readable explanation of the outcome. |
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 inthe 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-add
or order-sign
, 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.
The activityParameters
property conveys parameters specific to an activity
type. See the SMART Web Messaging Activity Catalog for details.
Note: A SMART app launched in the context of CDS Hooks should generally not
need to specify an activityType
or activityParameters
with the ui.done
message, because the EHR tracks the context in which the app was launched (e.g.,
order entry) and can navigate to an appropriate follow-up screen based on this
context.
An example of a ui.done
message from an app to the EHR is shown below:
targetWindow.postMessage({
"messageId": "<some new guid>",
"messageType": "ui.done",
"payload": {
"activityType": "problem-add",
"activityParameters": {
// Each ui activity defines its optional and required params. See the
// Activity Catalog for more details.
"problem": {
"resourceType": "Condition",
"patient": "123",
}
}
}
}, targetOrigin);
Similarly, the SMART app can use the ui.LaunchActivity
message type to request
navigation to a different activity without closing the app:
targetWindow.postMessage({
"messageId": "<some new guid>",
"messageType": "ui.launchActivity",
"payload": {
"activityType": "problem-add",
"activityParameters": {
// Each ui activity defines its optional and required params. See the
// Activity Catalog for more details.
"problem": {
"resourceType": "Condition",
"patient": "123",
}
}
}
}, targetOrigin);
The EHR SHALL respond to all ui
message types with a payload that includes a
boolean success
parameter and an optional details
string:
clientAppWindow.postMessage({
"messageId": "<some new guid>",
"responseToMessageId": "<guid from the client's request>",
"payload": {
"success": true,
"details": "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
scratchpad.*
Property | Optionality | Type | Description |
---|---|---|---|
resource |
CONDITIONAL | object | REQUIRED for scratchpad.create and scratchpad.update . Prohibited for scratchpad.delete . Conveys resource content as per CDS Hooks Action’s payload.resource . |
scratchpad.*
The EHR responds to all scratchpad
message types with a payload that matches
FHIR’s Bundle.entry.response
data model. The table below includes only
the most commonly used fields; see the FHIR specification for full details.
Property | Optionality | Type | Description |
---|---|---|---|
status |
REQUIRED | string | An HTTP response code (i.e. “200 OK”). |
location |
CONDITIONAL | string | REQUIRED if a new resource has been added to the scratchapd. Conveys a relative resource URL for the new resource. |
outcome |
OPTIONAL | object | FHIR OperationOutcome with details if something has gone wrong. |
The following example adds a new ServiceRequest
to the EHR’s scratchpad:
targetWindow.postMessage({
"messageId": "<some new guid>",
"messageType": "scratchpad.create",
"payload": {
"resource": {
"resourceType": "ServiceRequest",
"status": "draft",
// additional details as needed
}
}
}, targetOrigin);
For example, a proposal to update a draft prescription in the context of a CDS Hooks request might look like:
// Update to a better, cheaper alternative prescription
targetWindow.postMessage({
"messageId": "<some new guid>",
"messageType": "scratchpad.update",
"payload": {
"resource": {
"resourceType": "MedicationRequest",
"id": "123",
"status": "draft"
// additional details as needed
}
}
}, targetOrigin);
As described above, the EHR responds to all scratchpad
message types with a
payload that matches FHIR’s Bundle.entry.response
data model. For instance,
the response to a scratchpad.create
that adds a new prescription to the
scratchpad (and assigns id 456
to this draft resource) might look like:
clientAppWindow.postMessage({
"messageId": "<some new guid>",
"responseToMessageId": "<guid from the client's request>",
"payload": {
"status": "200 OK",
"location": "MedicationRequest/456"
}
}, clientAppOrigin);
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 expire the
handle when appropriate (e.g., the server might expire the handle when the user
session ends).
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 strategoy 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.launchActivity+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_messaging_origin
values:
{
"access_token": "i8hweunweunweofiwweoijewiwe",
"token_type": "bearer",
"expires_in": 3600,
"scope": "patient/Observation.read patient/Patient.read messaging/ui.launchActivity",
"smart_web_messaging_handle": "bws8YCbyBtCYi5mWVgUDRqX8xcjiudCo",
"smart_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.
In the current proposal, we leave discovery out of band. For example, a client
must consult server documentation to determine which message types a server
supports. We welcome ballot comments that consider whether we should define an
in-band way to advertise which message types (and possibly which parameters) a
server supports (e.g. via added details in a .well-known/smart-configuration
).
In the current proposal, we omit any initial handshake; a client can submit a Web Messaging request at any point, and can determine whether a connection is working based on a combination of responses and/or timeout logic. We welcome ballot comments that consider the utility of an explicit handshake, taking into account the fact that a initially working connection (e.g., at handshake time) can always degrade later.
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. We refer commenters to discussion and rationale here
and welcome any additional feedback on this point.
In the current proposal, we limit message types to ui
and scratchpad
for messages sent from the app to the EHR client. However, it might be convenient for apps if the SMART Web Messaging standard supported a fhir
message type, which would signify messages meant to be relayed from the app, through the EHR client to the FHIR server. We welcome ballot comments that speak to the merits or risks of this capability; based on feedback we will consider introducing a fhir.*
message type.
FHIRCast and CDS Hooks specify their events (or hooks) in separate specifications, using their own maturity models and lifecycles. Should the SMART Web Messaging adopt similar patterns for its message types and activities?