Skip to content

Commit 00cfa68

Browse files
committed
FHIR R5 Subscriptions
1 parent b38cb4a commit 00cfa68

3 files changed

Lines changed: 312 additions & 0 deletions

File tree

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
---
2+
features: [Analytics, Subscriptions, FHIR R5]
3+
---
4+
# FHIR R5 Topic-Based Subscriptions
5+
6+
This example demonstrates how to implement FHIR R5 topic-based subscriptions in Aidbox.
7+
8+
## Overview
9+
10+
The example shows how to:
11+
12+
- Define a custom `SubscriptionTopic` for ClaimResponse resources
13+
- Configure an `AidboxTopicDestination` for R5-compliant subscriptions
14+
- Create a `Subscription` that filters events by organization
15+
- Receive webhook notifications when matching resources are created or updated
16+
17+
## Components
18+
19+
### 1. AidboxSubscriptionTopic
20+
21+
Defines what events trigger notifications:
22+
23+
- **Resource**: `ClaimResponse`
24+
- **Trigger Criteria**: `status = 'active'`
25+
- **Filter**: Can filter by insurer organization identifier
26+
27+
### 2. AidboxTopicDestination
28+
29+
Configures the R5 subscription behavior:
30+
31+
- **Kind**: `fhir-native-topic-based-subscription`
32+
- **Content**: `full-resource` (sends complete resource in notifications)
33+
- **Deliverers**: 2 parallel workers
34+
- **Event Retention**: 24 hours (86400 seconds)
35+
36+
### 3. Subscription
37+
38+
The actual subscription that listens for events:
39+
40+
- **Topic**: Da Vinci PAS SubscriptionTopic
41+
- **Filter**: Only ClaimResponse for `Organization/org-1`
42+
- **Endpoint**: `http://echo-server:9090/webhook`
43+
- **Channel**: REST hook (webhook)
44+
- **Heartbeat**: Every 60 seconds
45+
- **Timeout**: 30 seconds
46+
47+
### 4. Echo Server
48+
49+
A simple HTTP echo service that:
50+
51+
- Logs all incoming webhook requests
52+
- Responds with request details
53+
- Runs on port 9090
54+
55+
## Getting Started
56+
57+
```bash
58+
docker-compose up -d
59+
```
60+
61+
Open [Aidbox](http://localhost:8080), and activate your Aidbox.
62+
63+
### Verify Subscription Status
64+
65+
```bash
66+
GET /fhir/Subscription/pas-subscription-r5
67+
```
68+
69+
The subscription should have `status: "active"` once Aidbox processes it.
70+
71+
## Testing the Subscription
72+
73+
This ClaimResponse will trigger a notification because it:
74+
75+
- Has `status: "active"`
76+
- References `Organization/org-1` as the insurer
77+
78+
```bash
79+
POST http://localhost:8080/fhir/ClaimResponse
80+
81+
{
82+
"resourceType": "ClaimResponse",
83+
"status": "active",
84+
"type": {
85+
"coding": [{
86+
"system": "http://terminology.hl7.org/CodeSystem/claim-type",
87+
"code": "institutional"
88+
}]
89+
},
90+
"use": "preauthorization",
91+
"patient": {
92+
"reference": "Patient/example"
93+
},
94+
"created": "2024-01-15T10:00:00Z",
95+
"insurer": {
96+
"reference": "Organization/org-1"
97+
},
98+
"outcome": "complete"
99+
}
100+
```
101+
102+
### 2. Check Echo Server Logs
103+
104+
View the webhook notification received by the echo server:
105+
106+
```bash
107+
docker-compose logs echo-server
108+
```
109+
110+
You should see a POST request to `/webhook` with the full ClaimResponse resource in the notification bundle.
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
volumes:
2+
postgres_data: {}
3+
services:
4+
postgres:
5+
image: docker.io/library/postgres:18
6+
volumes:
7+
- postgres_data:/var/lib/postgresql/18/docker:delegated
8+
command:
9+
- postgres
10+
- -c
11+
- shared_preload_libraries=pg_stat_statements
12+
environment:
13+
POSTGRES_USER: aidbox
14+
POSTGRES_PORT: '5432'
15+
POSTGRES_DB: aidbox
16+
POSTGRES_PASSWORD: jKPdGnUgs4
17+
aidbox:
18+
image: docker.io/healthsamurai/aidboxone:edge
19+
pull_policy: always
20+
volumes:
21+
- ./:/resources
22+
depends_on:
23+
- postgres
24+
ports:
25+
- 8080:8080
26+
environment:
27+
BOX_INIT_BUNDLE: 'file:///resources/init-bundle.json'
28+
BOX_ADMIN_PASSWORD: admin
29+
BOX_BOOTSTRAP_FHIR_PACKAGES: hl7.fhir.r5.core#5.0.0
30+
BOX_COMPATIBILITY_VALIDATION_JSON__SCHEMA_REGEX: '#{:fhir-datetime}'
31+
BOX_DB_DATABASE: aidbox
32+
BOX_DB_HOST: postgres
33+
BOX_DB_PASSWORD: jKPdGnUgs4
34+
BOX_DB_PORT: '5432'
35+
BOX_DB_USER: aidbox
36+
BOX_FHIR_BUNDLE_EXECUTION_VALIDATION_MODE: limited
37+
BOX_FHIR_COMPLIANT_MODE: 'true'
38+
BOX_FHIR_CORRECT_AIDBOX_FORMAT: 'true'
39+
BOX_FHIR_CREATEDAT_URL: https://aidbox.app/ex/createdAt
40+
BOX_FHIR_SCHEMA_VALIDATION: 'true'
41+
BOX_FHIR_SEARCH_AUTHORIZE_INLINE_REQUESTS: 'true'
42+
BOX_FHIR_SEARCH_CHAIN_SUBSELECT: 'true'
43+
BOX_FHIR_SEARCH_COMPARISONS: 'true'
44+
BOX_FHIR_TERMINOLOGY_ENGINE: hybrid
45+
BOX_FHIR_TERMINOLOGY_ENGINE_HYBRID_EXTERNAL_TX_SERVER: https://tx.health-samurai.io/fhir
46+
BOX_FHIR_TERMINOLOGY_SERVICE_BASE_URL: https://tx.health-samurai.io/fhir
47+
BOX_MODULE_SDC_STRICT_ACCESS_CONTROL: 'true'
48+
BOX_ROOT_CLIENT_SECRET: VR1aBoB8qR
49+
BOX_SEARCH_INCLUDE_CONFORMANT: 'true'
50+
BOX_SECURITY_DEV_MODE: 'true'
51+
BOX_SETTINGS_MODE: read-write
52+
BOX_WEB_BASE_URL: http://localhost:8080
53+
BOX_WEB_PORT: 8080
54+
healthcheck:
55+
test: curl -f http://localhost:8080/health || exit 1
56+
interval: 5s
57+
timeout: 5s
58+
retries: 90
59+
start_period: 30s
60+
echo-server:
61+
image: docker.io/mendhak/http-https-echo:latest
62+
ports:
63+
- 9090:9090
64+
environment:
65+
HTTP_PORT: 9090
66+
LOG_WITHOUT_NEWLINE: 'false'
67+
LOG_IGNORE_PATH: ''
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
{
2+
"type": "transaction",
3+
"resourceType": "Bundle",
4+
"entry": [
5+
{
6+
"resource": {
7+
"resourceType": "AidboxSubscriptionTopic",
8+
"id": "pas-claim-response",
9+
"url": "http://example.org/SubscriptionTopic/pas-claim-response",
10+
"status": "active",
11+
"trigger": [
12+
{
13+
"resource": "ClaimResponse",
14+
"fhirPathCriteria": "status = 'active'",
15+
"canFilterBy": [
16+
{
17+
"description": "Filter by insurer organization",
18+
"filterParameter": "orgIdentifier",
19+
"filterDefinitionFhirPathExpression": "insurer",
20+
"comparator": [
21+
"eq"
22+
]
23+
}
24+
]
25+
}
26+
]
27+
},
28+
"request": {
29+
"method": "POST",
30+
"url": "/AidboxSubscriptionTopic",
31+
"ifNoneExist": "_id=pas-claim-response"
32+
}
33+
},
34+
{
35+
"resource": {
36+
"resourceType": "AidboxTopicDestination",
37+
"id": "pas-claim-response-destination",
38+
"meta": {
39+
"profile": [
40+
"http://aidbox.app/StructureDefinition/aidboxtopicdestination-fhir-native-topic-based-subscription"
41+
]
42+
},
43+
"kind": "fhir-native-topic-based-subscription",
44+
"topic": "http://example.org/SubscriptionTopic/pas-claim-response",
45+
"status": "active",
46+
"content": "full-resource",
47+
"parameter": [
48+
{
49+
"name": "subscription-specification-version",
50+
"valueString": "R5"
51+
},
52+
{
53+
"name": "fhir-topic",
54+
"valueCanonical": "http://hl7.org/fhir/us/davinci-pas/SubscriptionTopic/PASSubscriptionTopic"
55+
},
56+
{
57+
"name": "number-of-deliverer",
58+
"valueUnsignedInt": 2
59+
},
60+
{
61+
"name": "keep-events-for-period",
62+
"valueUnsignedInt": 86400
63+
}
64+
]
65+
},
66+
"request": {
67+
"method": "POST",
68+
"url": "/AidboxTopicDestination",
69+
"ifNoneExist": "_id=pas-claim-response-destination"
70+
}
71+
},
72+
{
73+
"resource": {
74+
"resourceType": "Organization",
75+
"id": "org-1",
76+
"name": "Example Insurance Company"
77+
},
78+
"request": {
79+
"method": "POST",
80+
"url": "/Organization",
81+
"ifNoneExist": "_id=org-1"
82+
}
83+
},
84+
{
85+
"resource": {
86+
"resourceType": "Patient",
87+
"id": "example",
88+
"name": [
89+
{
90+
"family": "Test",
91+
"given": [
92+
"Patient"
93+
]
94+
}
95+
]
96+
},
97+
"request": {
98+
"method": "POST",
99+
"url": "/Patient",
100+
"ifNoneExist": "_id=example"
101+
}
102+
},
103+
{
104+
"resource": {
105+
"resourceType": "Subscription",
106+
"status": "requested",
107+
"reason": "Receive notifications about claim responses for my organization.",
108+
"topic": "http://hl7.org/fhir/us/davinci-pas/SubscriptionTopic/PASSubscriptionTopic",
109+
"filterBy": [
110+
{
111+
"filterParameter": "orgIdentifier",
112+
"comparator": "eq",
113+
"value": "Organization/org-1"
114+
}
115+
],
116+
"channelType": {
117+
"system": "http://terminology.hl7.org/CodeSystem/subscription-channel-type",
118+
"code": "rest-hook"
119+
},
120+
"endpoint": "http://echo-server:9090/webhook",
121+
"heartbeatPeriod": 60,
122+
"timeout": 30,
123+
"id": "pas-subscription-r5",
124+
"contentType": "application/fhir+json",
125+
"content": "full-resource",
126+
"maxCount": 10
127+
},
128+
"request": {
129+
"method": "POST",
130+
"url": "/Subscription",
131+
"ifNoneExist": "_id=pas-subscription-r5"
132+
}
133+
}
134+
]
135+
}

0 commit comments

Comments
 (0)