OZO FHIR implementation guide
0.6.3 - ci-build
OZO FHIR implementation guide - Local Development build (v0.6.3) built by the FHIR (HL7® FHIR® Standard) Build Tools. See the Directory of published versions
Team-to-team messaging enables organizations (pharmacies, clinics, hospitals) to communicate as units while maintaining individual auditability. This pattern uses the OZOOrganizationalCareTeam profile to represent organizational teams — these are CareTeams without a patient subject, linked to a managing Organization.
For individual messaging (RelatedPerson ↔ Practitioner), see Individual Messaging.
CommunicationRequest uses the senderCareTeam extension to specify a CareTeam as the reply-to address. This is needed because FHIR R4 CommunicationRequest.sender does not allow CareTeam references. The extension:
CommunicationRequest.requester is always an individual (who initiated the thread)CommunicationRequest.sender is always an individual (same as requester)Communication.sender is always an individual (who sent each message)senderCareTeam extension on the CommunicationRequest identifies the initiating team. The OZO FHIR Api uses this to determine which team members receive Tasks when a reply is created.This IG distinguishes the following roles when processing team-to-team messages:
CommunicationRequest, Communication, Task and AuditEvent.Both teams use the OZO platform. Unlike individual messaging, there is no OZO client involved — both sides are practitioners.
The following Subscription objects are created by the OZO platform for each team:
CommunicationRequest?idCommunication?idTask?status=requested (the AAA proxy automatically scopes this to the current user's ownership)In the Netherlands, healthcare data must not be pushed in subscription notifications. All subscriptions use the notify-then-pull pattern:
This means channel.payload must be left empty. The notification only signals that something matched the subscription criteria — the subscriber is responsible for fetching the actual data.
Each subscription serves a different purpose. Understanding when notifications fire is critical for correct client implementation:
| Subscription | Purpose | Fires when |
|---|---|---|
Communication?id |
New message notification. This is the primary mechanism for detecting new messages in a thread. | A new Communication is created (POST). |
CommunicationRequest?id |
Thread lifecycle changes. | A CommunicationRequest is created or its status changes. |
Task?status=requested |
Read status changes. Not suitable for new-message detection. | A Task status changes to REQUESTED (e.g. COMPLETED → REQUESTED). |
Important: The
Taskresource functions as a read/unread indicator, not as a message notification mechanism. When a new message arrives and the Task is already in statusrequested(unread), the OZO FHIR Api sets it torequestedagain — which is a no-op. HAPI FHIR does not create a new resource version when nothing changes, so no subscription notification is sent.To detect new messages, clients must subscribe to
Communication. TheTasksubscription is only useful for observing read-status transitions.
A practitioner from Team A creates a new thread addressed to Team B. The process looks as follows:
CommunicationRequest object, the following fields are set:
requester is the Practitioner who initiates the conversation (for auditability)sender is the same Practitioner (individual sender)extension[senderCareTeam] is set to the CareTeam of Team A (reply-to address for team-level authorization)subject is the Patient referencerecipient is the CareTeam of Team Bstatus is set to ACTIVEpayload contains the initial messageTask for each member of Team B's CareTeam:
status is set to REQUESTEDintent is set to ORDERbasedOn is set to the CommunicationRequest referencesubject is set to the Patient referenceowner is set to the individual CareTeam memberCommunicationRequest and Task by Subscription:
CommunicationRequest subscription notifies Team B of the new threadTask (status REQUESTED) tracks the unread state per team memberA practitioner from Team B responds to the thread. The reply is addressed to Team A's CareTeam, which is discovered from the senderCareTeam extension on the original CommunicationRequest.
Communication with the following fields:
partOf is set to the reference of the CommunicationRequestinResponseTo is set to the reference of the previous Communication being replied tosender is set to the Practitioner from Team B (individual auditability)payload consists of text and optionally attachmentsstatus is set to COMPLETEDrecipient is not set — thread participants are defined on the CommunicationRequest.CareTeam (the recipient) and not the sender:
status is set to REQUESTEDintent is set to ORDERbasedOn is set to the CommunicationRequest referencesubject is set to the Patient referenceowner is set to the individual CareTeam memberCareTeam who is not the sender:
Communication by Subscription:
Task status change (COMPLETED → REQUESTED) updates the unread indicator, but only if the previous message was already read. If the Task was already REQUESTED, no Task notification is sent — the Communication subscription ensures the message is always detected.A different practitioner from Team A follows up on the thread. This demonstrates that any team member can participate in the conversation.
Communication with the following fields:
partOf is set to the reference of the CommunicationRequestinResponseTo is set to the reference of the previous Communicationsender is set to the different Practitioner from Team A (individual auditability — note this is a different person than the original requester)payload consists of text and optionally attachmentsstatus is set to COMPLETEDWhen a practitioner reads a message in a team thread:
AuditEvent with the following properties:
type is set to http://terminology.hl7.org/CodeSystem/iso-21089-lifecycle|accessaction is set to 'R'recorded field is set to the current timestampagent.who field is set to the Practitioner who read the messageentity.what field has two values:
CommunicationCommunicationRequestTask is queried for the Practitioner as part of the agent.who of the AuditEventTask status is set to COMPLETEDCareTeam reads the message, the message is marked as read for all the members in the CareTeam.The diagram below displays the team-to-team messaging flow, including thread creation and responses from both teams. {::nomarkdown}
{:/}
The following walkthrough shows a concrete example with Apotheek de Pil (Pharmacy A) and Huisarts Amsterdam (Clinic B).
A pharmacist (A.P. Otheeker) from Apotheek de Pil sends a message to Huisarts Amsterdam about a patient's medication — see Pharmacy-to-Clinic for the full CommunicationRequest.
The OZO FHIR Api creates a Task (status requested) for each Clinic B member — see Notify-Manu-van-Weel for an example of a Task resource.
Notifications fired:
CommunicationRequest subscription → Clinic B notified of new threadTask?status=requested subscription → each Clinic B practitioner notified of unread threadDr. Manu van Weel from the clinic reads the message. The OZO platform creates an AuditEvent — see Manu-Read-Messages for a similar example.
The OZO FHIR Api marks the Tasks as completed. Because this is a team message, all Clinic B Tasks are completed (team-wide read):
completed (was: requested)completed (was: requested, team-wide read)completed (was: requested, team-wide read)Manu then replies. The reply goes to the pharmacy team (read from CommunicationRequest.extension[senderCareTeam]) — see Clinic-Response-to-Pharmacy for the full Communication.
The OZO FHIR Api creates/updates Tasks for Pharmacy A members:
requestedrequestedNotifications fired:
Communication subscription → Pharmacy A practitioners notified of new messageTask?status=requested subscription → each Pharmacy A practitioner notified of unread messageA.P. Otheeker has not read the reply yet (Task still REQUESTED). Pieter de Vries reads it and responds, demonstrating that any team member can participate — see Pharmacy-Followup-by-Pieter for the full Communication.
The OZO FHIR Api updates Tasks:
Pharmacy A Tasks:
completed (was: requested, team-wide read)completed (Pieter is the sender)Clinic B Tasks:
requested (was: completed)requested (was: completed)requested (was: completed)Notifications fired:
Communication subscription → Clinic B practitioners notified of new message (always fires)Task?status=requested subscription → each Clinic B practitioner notified (status changed from COMPLETED to REQUESTED)Note: If Clinic B had not yet read the previous message (Tasks still REQUESTED), the Task update would be a no-op and no Task notification would be sent. The
Communicationsubscription ensures the new message is always detected regardless of read status.
Find messages for my team (via thread membership):
GET /Communication?part-of:CommunicationRequest.recipient=CareTeam/Pharmacy-A&_include=Communication:based-on
Find all messages in a thread:
GET /Communication?based-on=CommunicationRequest/thread-id&_sort=sent
Find messages I sent:
GET /Communication?sender=Practitioner/my-id
Find threads initiated by my team:
GET /CommunicationRequest?_has:Extension:url=http://ozoverbindzorg.nl/fhir/StructureDefinition/ozo-sender-careteam&sender-careteam=CareTeam/Pharmacy-A
For detailed analysis of the addressing solution, see FHIR Addressing Analysis.