Ovok blog

Real-Time FHIR: Subscriptions, WebSockets and FHIRcast

Real-time FHIR explained: R4 Subscriptions over rest-hook and WebSocket, the R5 topic backport, FHIRcast, polling vs push, and WebSocket subscriptions on Ovok.

Real-time FHIR means your app is told when data changes instead of asking the server over and over. The standard way to do it is a FHIR Subscription: you register search criteria, and the server sends a notification over a channel such as a rest-hook (webhook) or a WebSocket whenever a matching resource is created or updated. This guide explains FHIR R4 Subscriptions, the topic-based model from R5 and its R4/R4B backport, FHIRcast for context synchronization, polling vs push trade-offs, scaling concerns, and how to use WebSocket subscriptions on Ovok.

Key takeaways

  • A FHIR Subscription pairs criteria (what to watch) with a channel (how to notify). In R4 the criteria is a search expression such as Observation?patient=Patient/123.
  • Use rest-hook when a server receives notifications and websocket when an open app (browser or mobile) does.
  • R5 replaced free-form criteria with SubscriptionTopic. The Subscriptions R5 Backport IG brings this model to R4 and R4B.
  • FHIRcast is a separate HL7 standard for syncing user context (which patient or study is open) between apps, not for data-change notifications.
  • Treat notifications as hints: deduplicate, re-fetch with a search, and keep a polling fallback for missed events.
  • On Ovok, WebSocket Subscriptions are a project feature (websocket-subscriptions) and the socket is served on the API host.

Why real-time matters in digital health

A clinician dashboard that refreshes every minute shows a new blood-pressure reading up to a minute late. A patient app that polls every few seconds wastes battery and API quota. In remote monitoring, alerting and care coordination, the gap between "data written" and "someone sees it" matters. Push-based FHIR Subscriptions close that gap with a standard mechanism rather than a vendor-specific event bus.

If you are new to FHIR, start with What is FHIR?. Subscriptions build on FHIR search, so our FHIR API search guide is useful background.

FHIR R4 Subscriptions

The R4 Subscription resource is marked Trial Use. Its key elements are:

  • status: requested, active, error or off. A client creates the subscription as requested; the server sets it to active once accepted, and to error if notifications fail.
  • reason: a human-readable description of why it exists.
  • criteria: a FHIR search expression, for example Observation?code=http://loinc.org|85354-9.
  • channel: the delivery method, with type, endpoint, payload and header.
  • end: an optional time after which the subscription is removed.

Channel types in R4

The R4 specification defines five channel types:

  • rest-hook: the server sends an HTTP POST to your endpoint. With no payload, the body is empty and your client re-runs the criteria as a search (for example with _since) to fetch what changed. With a payload MIME type, the server sends the matching resource.
  • websocket: the client opens a WebSocket to the server, sends bind :id with the subscription's id, and the server answers bound :id. After that the server sends ping :id each time new results are available, and the client fetches them.
  • email and sms: human-readable notifications to an address or number.
  • message: the resource is sent as a FHIR message to an application.

Note the design: in plain R4, a WebSocket notification tells you "something changed", not what changed. Your client still performs a search. That keeps sensitive data off the socket, at the cost of an extra request.

Topic-based subscriptions: R5 and the R4/R4B backport

R4's free-form criteria proved hard to implement consistently, so FHIR R5 redesigned Subscriptions around topics. A SubscriptionTopic resource, published by the server, defines an event such as "an encounter started". Clients subscribe to a topic and narrow it with filters. Notifications arrive as a Bundle with a SubscriptionStatus entry that describes the event.

The Subscriptions R5 Backport IG lets R4 and R4B servers implement a subset of this model in a standard way, through profiles, extensions and operations. The latest trial-use release is 1.1.0, and 1.2.0 has been balloted. Points that matter for developers:

Payload types

The backport defines three payload levels:

  • empty: no information about the resources; the client queries the server. Lowest risk, because the notification carries no PHI.
  • id-only: resource references without the content. The IG describes this as a good balance between security and performance.
  • full-resource: the resources are included. Use it only over a secure channel.

Channels, handshakes and heartbeats

The backport channels page defines rest-hook, websocket, email and a draft message channel. Websocket is the only channel where the client needs no endpoint of its own, which is why it suits mobile and browser apps. The websocket flow is:

  1. Create a Subscription with channel.type set to websocket.
  2. Call the $get-ws-binding-token operation. The server returns a token, an expiration and a websocket-url.
  3. Connect to that URL and send bind-with-token with the token.
  4. The server replies with a handshake notification for each bound subscription.
  5. Request a new token before it expires, and reconnect if the URL changes.

Servers may also send heartbeat notifications so a client can tell that a quiet connection is still alive.

FHIRcast: real-time context, not data changes

FHIRcast solves a different problem. When a radiologist opens a study in a reporting system, the PACS viewer and the dictation app should switch to the same patient and study. FHIRcast synchronizes that user context across applications. It is modeled on webhooks and W3C WebSub and builds on the SMART on FHIR launch: apps learn the hub URL and a session hub.topic at launch, subscribe to events such as Patient-open or ImagingStudy-open, and the hub notifies them when the context changes.

The latest trial-use version is STU2 (2.0.0, 2021), and an STU3 ballot was published in 2024. In STU2, hubs must support WebSockets and may support webhooks: a subscriber posts a subscription with hub.channel.type=websocket, receives a wss endpoint in the response, connects, and acknowledges each event it receives.

Use Subscriptions when you need to know that data changed. Use FHIRcast when several apps used by the same person must show the same patient at the same time.

Polling vs push: choosing the right approach

Push is not automatically better. Here is how the options compare:

  • Polling (GET /Observation?patient=...&_lastUpdated=gt...): simple, stateless and works through any proxy. Costs grow with the number of clients and the polling frequency, and latency equals the interval.
  • Rest-hook subscriptions: low latency and efficient for server-to-server integrations. Your receiver must be reachable, authenticated and idempotent.
  • WebSocket subscriptions: low latency for apps that stay open, no public endpoint needed. You manage connection lifecycles, reconnects and token renewal.

A common production pattern combines them: WebSockets for open dashboards, rest-hooks or server-side handlers for back-end processing, and a periodic catch-up search for anything missed.

Scaling and reliability concerns

Real-time systems fail in quiet ways. Plan for these from the start:

  1. Notifications can be missed. Networks drop, apps go to the background and servers restart. After every reconnect, run a catch-up search with _lastUpdated or _since from the last time you processed data.
  2. Notifications can repeat. Make handlers idempotent: key on resource id and version (meta.versionId).
  3. Order is not guaranteed. Compare meta.lastUpdated or version ids instead of trusting arrival order.
  4. Fan-out grows fast. One busy patient feed watched by many dashboards multiplies events. Narrow criteria to what each screen needs.
  5. Connection limits exist. Servers cap idle and concurrent sockets. Reuse one connection per app session where possible and back off on errors.
  6. Keep PHI off notifications when you can. Empty or id-only payloads keep sensitive data inside authenticated API calls.
  7. Authorization still applies. A subscription should only deliver what its owner is allowed to read. Test this with restricted users.
  8. Watch subscription status. A subscription in error or off means nothing is being delivered. Monitor it.

How to do real-time FHIR on Ovok

Ovok supports FHIR R4 Subscriptions, including the WebSocket channel, through the Ovok API. WebSocket delivery is controlled by the `websocket-subscriptions` project feature, which is on by default for new projects. Subscriptions with a rest-hook channel do not need it.

The steps below are adapted from the Ovok documentation and use the sandbox host.

1. Check that the feature is enabled

Projects created before the feature became a default can lack it. Read the list with GET https://api.sandbox.ovok.com/v1/projects/me/features. A project admin turns it on with a PATCH that replaces the whole list:

  • curl --request PATCH --url 'https://api.sandbox.ovok.com/v1/projects/me/features' --header "Authorization: Bearer ${OVOK_TOKEN}" --header 'Content-Type: application/json' --data '{"features":["bots","cron","email","transaction-bundles","websocket-subscriptions"]}'

In a project hierarchy, the feature is checked on the project the data belongs to, so enable it on each project whose subscriptions you expect to be delivered.

2. Create a Subscription

Create the resource with POST https://api.sandbox.ovok.com/fhir/R4/Subscription, using the R4 elements described on the Subscription reference page. For example:

  • {"resourceType":"Subscription","status":"active","reason":"Live readings for one patient","criteria":"Observation?patient=Patient/123","channel":{"type":"websocket"}}

3. Request a binding token

The binding token ties the socket to the subscription and to the identity that asked for it:

  • curl --get "https://api.sandbox.ovok.com/fhir/R4/Subscription/${SUBSCRIPTION_ID}/\$get-ws-binding-token" --header "Authorization: Bearer ${OVOK_TOKEN}"

Its websocket-url parameter names the socket. The subscription must exist and be readable by the caller.

4. Connect to the socket

WebSocket connections go to the API host, the same host as every other Ovok call. In the sandbox that is wss://api.sandbox.ovok.com/ws/subscriptions-r4. Use the path exactly as given: an upgrade with a query string or a trailing slash is refused. In a web app, the React SDK's OvokClient.getSubscriptionManager() returns the manager for WebSocket subscriptions.

Limits to design for

  • A connection with no traffic in either direction for 60 seconds is dropped. A bound socket receives a heartbeat every 10 seconds, so only sockets that never bind, or are abandoned, hit this.
  • Too many open sockets from one client address get 429 until some close. Clients behind one NAT share that limit.
  • Turning the feature off stops delivery without deleting your Subscriptions.

When to use rest-hooks or Bots instead

The Ovok docs recommend rest-hook when a server is the receiver: WebSocket delivery suits apps that stay open, while a back end is better served by a webhook or a Bot (which needs the bots feature). Combine this with Access Policies so each user's subscriptions only deliver data they are allowed to read.

Where this fits in a monitoring product

In a remote-monitoring app, a clinician dashboard can subscribe to new Observations for the patients on screen, while back-end processing uses rest-hooks. For the bigger picture of devices, records and alerting, see our guide to the best remote patient monitoring platform and the remote patient monitoring use case.

FAQ

What is a FHIR Subscription?

A FHIR Subscription is a resource that tells a server to notify a client when data matching certain criteria is created or updated. It defines what to watch and the channel to use, such as a rest-hook or WebSocket.

What is the difference between rest-hook and websocket channels?

A rest-hook sends an HTTP POST to an endpoint you host, which suits servers. A WebSocket channel delivers notifications over a connection the client opens, which suits browser and mobile apps that have no public endpoint.

Does a FHIR WebSocket notification contain the changed resource?

In plain R4, no: the server sends a ping and the client searches for new data. With the R5 model or the backport IG, the payload can be empty, id-only or full-resource, depending on what the server supports and the subscription requests.

Can I use topic-based subscriptions on an R4 server?

Yes, if the server implements the Subscriptions R5 Backport IG, which brings a subset of R5 topic-based subscriptions to R4 and R4B.

Is FHIRcast the same as FHIR Subscriptions?

No. FHIRcast synchronizes user context, such as the open patient or imaging study, between applications. FHIR Subscriptions notify systems when data changes.

How do I avoid missing notifications?

Treat notifications as hints. After each reconnect, run a catch-up search based on the last update you processed, make handlers idempotent, and monitor subscription status.

Is polling ever the better choice?

Yes. For low-frequency data, a few clients, or environments where long-lived connections are blocked, polling with _lastUpdated is simpler and reliable enough.

Conclusion

Real-time FHIR is built from standard parts: Subscriptions for data changes, rest-hooks for servers, WebSockets for open apps, topic-based subscriptions for richer events, and FHIRcast for shared user context. Design for missed and repeated events from day one. To try WebSocket Subscriptions on a FHIR R4 backend hosted in Frankfurt, create a free Developer sandbox, follow the Ovok docs, and see pricing when you are ready for production.

Keep reading