SMART Web Messaging
1.0.0 - trial-use

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

Smart Web Messaging

Considerations{::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.

Conformance Language

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

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.

Request Parameters

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.

Response Properties

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.

Response Payload

The response message payload properties will vary based on the request messageType. See message types below for details.

Response Target Origin

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

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).

Establish Connection Status: status.* Message Type

Handshake

The 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.

Example Handshake Sequence

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);
  }
//...
});

Influence the EHR UI: ui.* Message Type

An 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.
  • Data can be passed from the app to the EHR as hints, within the launchActivity payload.
  • For supported resource types, it’s better to pass in a scratchpad resource ID rather than duplicating resource content when providing a hint to launchActivity.

Request Payload for 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);

Response Payload for 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);

EHR Scratchpad Interactions: scratchpad.* Message Type

While 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:

  • CDS Hooks Action type is used to populate the response messageType
    • createscratchpad.create
    • updatescratchpad.update
    • deletescratchpad.delete
  • CDS Hooks Action resource: used to populate the payload.resource

What is a Scratchpad?

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 Operations

Scratchpad 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
Request payload
Property Optionality Type Description
resource REQUIRED object Conveys resource content as per the CDS Hooks Action payload.resource.
Response 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.
Examples

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.

Request payload
Property Optionality Type Description
location OPTIONAL string Takes the form resourceType/id when provided.
Response 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.

Examples

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
Request 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.

Response 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.
Examples

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
Request payload
Property Optionality Type Description
location REQUIRED string Takes the form resourceType/id.
Response 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.
Examples

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);

EHR FHIR Server Interactions: fhir.http Message Type

In 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
Request 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.
Response 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.
Example

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);

Authorization with SMART Scopes

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 medications
  • messaging/scratchpad: enable access to draft orders (including meds) on the EHR scratchpad
  • messaging/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.

Scope examples

 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"
}

Limitations

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.

Alternatives Considered

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.

See Alternatives Considered.

Security Considerations

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.

List of Abbreviations

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