POC IZNC - Integrated Care Network Communication
0.1.2 - ci-build
POC IZNC - Integrated Care Network Communication - Local Development build (v0.1.2) built by the FHIR (HL7® FHIR® Standard) Build Tools. See the Directory of published versions
Author: roland@headease.nl
This document is released under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.
This document describes an alternative architecture where the Chat Application communicates directly with the FHIR API instead of via a custom Matrix Bridge API. The existing matrix-bridge automatically synchronizes between Matrix and FHIR, allowing the FHIR server to function as the primary client API.
Core Idea: Use the OZO implementation of FHIR messaging (CommunicationRequest, Communication, Task) as the client API and let the matrix-bridge handle synchronization with Matrix.
Chat Backend → Matrix Bridge API → Matrix Bridge → Matrix Homeserver
↓
Database (BSN mapping, state)
Characteristics:
Chat Backend → FHIR Server ← Matrix Bridge → Matrix Homeserver
↓ ↓
└─────────┘
FHIR Subscriptions
(webhooks to Chat Backend)
Characteristics:
┌──────────────────────────────────────────┐
│ Chat Application Frontend │
│ - User login via DigID (BSN) │
│ - WebSocket for real-time updates │
└──────────────────┬───────────────────────┘
│
▼
┌──────────────────────────────────────────┐
│ Chat Application Backend │
│ - DigID authentication │
│ - BSN → Patient FHIR reference mapping │
│ - FHIR Subscription webhook endpoint │
└──┬───────────────────────────────────────┘
│ ▲
│ FHIR R4 REST API │ FHIR Subscription
│ GET /CareTeam? │ (webhook notifications)
│ patient=Patient/123 │
│ POST /CommunicationRequest │
│ POST /Communication │
│ │
▼ │
┌──────────────────────────────────────────┐
│ FHIR Server (HAPI FHIR) │
│ - CareTeam resources │
│ - CommunicationRequest (threads) │
│ - Communication (messages) │
│ - Task (read receipts) │
│ - AuditEvent (audit trail) │
│ - FHIR Subscriptions │
└───────────┬──────────────────────────────┘
│ ▲
│ │
│ │ FHIR Subscription
▼ │ (webhook notifications)
┌──────────────────────────────┐
│ Matrix Bridge (EXISTING) │
│ - Watches FHIR resources │
│ - Creates Matrix spaces │
│ - Creates Matrix rooms │
│ - Posts messages to Matrix │
│ - Updates FHIR from Matrix │
└───────────┬──────────────────┘
│ Matrix Protocol
▼
┌───────────────────────┐
│ Matrix Homeserver │
│ (Synapse) │
└───────────────────────┘
Chat Backend works with FHIR Patient reference instead of BSN
1. User login via DigID → BSN
2. Chat Backend: Resolve BSN → Patient FHIR reference
- Lookup: GET /Patient?identifier=urn:oid:2.16.840.1.113883.2.4.6.3|{bsn}
- Get: Patient/123
3. Chat Backend: Get care networks
GET /CareTeam?patient=Patient/123
(Note: This finds CareTeams where patient is the subject)
4. Return CareTeam resources with participants
Example Request:
GET /fhir/CareTeam?patient=Patient/123
Accept: application/fhir+json
Note: The OID urn:oid:2.16.840.1.113883.2.4.6.3 is the official identifier system for BSN (Burgerservicenummer) in the Netherlands.
Example Response:
{
"resourceType": "Bundle",
"entry": [
{
"resource": {
"resourceType": "CareTeam",
"id": "244",
"status": "active",
"subject": {
"reference": "Patient/123",
"display": "Jan Jansen"
},
"participant": [
{
"role": [{
"coding": [{
"system": "http://snomed.info/sct",
"code": "158965000",
"display": "Medical practitioner"
}]
}],
"member": {
"reference": "Practitioner/456",
"display": "Dr. Smith"
}
},
{
"role": [{
"coding": [{
"system": "http://terminology.hl7.org/CodeSystem/v3-RoleCode",
"code": "CHILD"
}]
}],
"member": {
"reference": "RelatedPerson/789",
"display": "Marie Jansen"
}
}
]
}
}
]
}
GET /CommunicationRequest?subject=Patient/123&_include=CommunicationRequest:recipient
Accept: application/fhir+json
Response: Bundle with all CommunicationRequests for this patient
POST /fhir/CommunicationRequest
Content-Type: application/fhir+json
{
"resourceType": "CommunicationRequest",
"status": "active",
"subject": {
"reference": "Patient/123"
},
"recipient": [
{
"reference": "Practitioner/456"
}
],
"sender": {
"reference": "RelatedPerson/789"
},
"payload": [
{
"contentString": "Schedule appointment for checkup"
}
]
}
Matrix Bridge detects new CommunicationRequest:
#communicationrequest-{id}:homeserverPOST /fhir/Communication
Content-Type: application/fhir+json
{
"resourceType": "Communication",
"status": "completed",
"partOf": [
{
"reference": "CommunicationRequest/245"
}
],
"sender": {
"reference": "RelatedPerson/789"
},
"recipient": [
{
"reference": "Practitioner/456"
}
],
"payload": [
{
"contentString": "Can I take this medication with food?"
}
],
"sent": "2025-01-15T11:00:00Z"
}
Matrix Bridge detects new Communication:
GET /fhir/Communication?part-of=CommunicationRequest/245&_sort=-sent
Accept: application/fhir+json
Response: Bundle with all Communications for this thread
POST /fhir/Task
Content-Type: application/fhir+json
{
"resourceType": "Task",
"status": "completed",
"intent": "order",
"focus": {
"reference": "Communication/567"
},
"owner": {
"reference": "Practitioner/456"
},
"executionPeriod": {
"end": "2025-01-15T11:05:00Z"
}
}
Matrix Bridge detects Task update:
POST /fhir/Subscription
Content-Type: application/fhir+json
{
"resourceType": "Subscription",
"status": "active",
"reason": "Monitor new messages in care network",
"criteria": "Communication?subject=Patient/123",
"channel": {
"type": "rest-hook",
"endpoint": "https://chat-backend.example.com/fhir/webhooks/sub-123",
"header": [
"Authorization: Bearer {token}"
]
}
}
With no payload field (omitted), FHIR server sends empty POST to subscription-specific endpoint:
POST /fhir/webhooks/sub-123
Authorization: Bearer {token}
(empty body)
Important: The notification contains no body. The subscription ID is in the URL path. The Chat Backend must fetch updates using _history with _since parameter:
GET /fhir/Communication/_history?_since=2025-01-13T10:30:00Z&subject=Patient/123
Accept: application/fhir+json
Implementation Pattern:
/fhir/webhooks/{subscriptionId}/fhir/webhooks/sub-123GET /fhir/Communication/_history?_since={lastSync}&subject=Patient/123Subscription 1: Communication?subject=Patient/123 (new messages)
Subscription 2: Task?owner=Practitioner/456 (read receipts)
Subscription 3: CommunicationRequest?subject=Patient/123 (new threads)
Chat Backend responsibility:
GET /Patient?identifier=http://fhir.nl/fhir/NamingSystem/bsn|{bsn}Advantages:
The existing Matrix Bridge continues to operate exactly as designed, with no modifications needed for the FHIR-First approach:
FHIR → Matrix Synchronization:
Matrix → FHIR Synchronization:
Two parallel notification paths:
This dual approach ensures:
// 1. Initial BSN lookup (once per login)
async function resolvePatient(bsn: string): Promise<string> {
const bundle = await fhirClient.search({
resourceType: 'Patient',
searchParams: {
identifier: `http://fhir.nl/fhir/NamingSystem/bsn|${bsn}`
}
});
return bundle.entry[0].resource.id; // "Patient/123"
}
// 2. Get care networks
async function getCareNetworks(patientRef: string): Promise<CareTeam[]> {
const bundle = await fhirClient.search({
resourceType: 'CareTeam',
searchParams: {
patient: patientRef
}
});
return bundle.entry.map(e => e.resource);
}
// 3. Get threads
async function getThreads(patientRef: string): Promise<CommunicationRequest[]> {
const bundle = await fhirClient.search({
resourceType: 'CommunicationRequest',
searchParams: {
subject: patientRef,
_sort: '-authored'
}
});
return bundle.entry.map(e => e.resource);
}
// 4. Send message
async function sendMessage(
commReqRef: string,
senderRef: string,
text: string
): Promise<Communication> {
return await fhirClient.create({
resourceType: 'Communication',
status: 'completed',
partOf: [{ reference: commReqRef }],
sender: { reference: senderRef },
payload: [{ contentString: text }],
sent: new Date().toISOString()
});
}
// 5. FHIR Subscription webhook handler
// Store last sync timestamp and criteria per subscription
const subscriptions = new Map<string, {
lastSync: string;
criteria: string; // e.g., "Communication?subject=Patient/123"
}>();
app.post('/fhir/webhooks/:subscriptionId', async (req, res) => {
const subscriptionId = req.params.subscriptionId;
// Acknowledge receipt immediately
res.status(200).send();
// Process notification asynchronously
const subscription = subscriptions.get(subscriptionId);
if (!subscription) {
console.error(`Unknown subscription: ${subscriptionId}`);
return;
}
// Get last sync time (or start from 1 hour ago if first time)
const since = subscription.lastSync ||
new Date(Date.now() - 3600000).toISOString();
// Fetch history since last sync
// Parse criteria to extract resource type and search params
const historyBundle = await fhirClient.search({
resourceType: 'Communication',
searchParams: {
_since: since,
subject: 'Patient/123' // Extracted from subscription.criteria
}
});
// Process all changed resources
for (const historyEntry of historyBundle.entry || []) {
const resource = historyEntry.resource as Communication;
// New message → notify frontend via WebSocket
const affectedUsers = await getUsersInThread(resource.partOf[0].reference);
affectedUsers.forEach(userId => {
wsConnections[userId].send({
type: 'message.new',
threadId: resource.partOf[0].reference,
message: transformToSimpleFormat(resource)
});
});
}
// Update last sync timestamp
subscription.lastSync = new Date().toISOString();
});
| Custom Matrix Bridge API | FHIR API Equivalent |
|---|---|
POST /care-networks/discover |
GET /CareTeam?patient={ref} |
POST /subscriptions |
POST /Subscription |
POST /threads |
POST /CommunicationRequest |
POST /threads/{id}/messages/search |
GET /Communication?part-of={ref} |
POST /threads/{id}/messages |
POST /Communication |
POST /threads/{id}/read |
POST /Task (status=completed) |
| Webhook notifications | FHIR Subscription rest-hook |
Custom API:
// Every call contains BSN
POST /api/v1/care-networks/discover
{ "bsn": "123456789" }
FHIR-First:
// Once at login: BSN → Patient reference
const patientRef = await resolvePatient("123456789");
// Cache in session: session.patientRef = "Patient/123"
// All subsequent calls use Patient reference
GET /fhir/CareTeam?patient=Patient/123
✅ No custom API development ✅ Standard FHIR compliance ✅ Matrix bridge stays simple (no database, no custom endpoints) ✅ Direct FHIR audit trail ✅ FHIR Subscriptions are standard ✅ Easier for others to adopt (just FHIR API)
❌ Chat Backend must understand FHIR
❌ More FHIR calls needed
❌ FHIR performance considerations
❌ BSN lookup still needed
Status: Alternative Architecture Proposal Last Update: 2025-01-13