examples

Topic-based Subscriptions to Kafka

Demo | Documentation

This example showcases Aidbox SubscriptionTopic producing data to Kafka.

Objectives:

  1. Set up Aidbox and Kafka locally using Docker Compose.
  2. Get FHIR QuestionnaireResponse via Aidbox Forms.
  3. Learn how AidboxSubscriptionTopic and AidboxTopicDestination work with Kafka to handle the collected data.

Table of Contents

Prerequisites

Step 1: Set Up the Environment

Set Up Aidbox

  1. Copy the .env.tpl file to .env:

     cp .env.tpl .env
    
  2. Get a self-hosted Aidbox license from the Aidbox Portal.

  3. Add the license key (AIDBOX_LICENSE) to the .env file.

Run Aidbox, Kafka & Kafka UI

docker compose up

The Docker Compose file initializes the environment for both Kafka and Aidbox with the following configuration:

Step 2: Set Up Subscription and Destination For QuestionnaireResponse

Create AidboxSubscriptionTopic Resource

To create a subscription on the QuestionnaireResponse resource that has a specific status, open Aidbox UI -> APIs -> REST Console and execute the following request:

POST /fhir/AidboxSubscriptionTopic
content-type: application/json
accept: application/json

{
  "resourceType": "AidboxSubscriptionTopic",
  "url": "http://example.org/FHIR/R5/SubscriptionTopic/QuestionnaireResponse-topic",
  "status": "active",
  "trigger": [
    {
      "resource": "QuestionnaireResponse",
      "fhirPathCriteria": "status = 'completed' or status = 'amended'"
    }
  ]
}

This resource describes the data source for the subscription but doesn’t execute any activities from Aidbox.

Create AidboxTopicDestination Resource

Creating this resource establishes a connection to the Kafka server. When the system produces an event, it will be processed to the specified Kafka topic.

POST /fhir/AidboxTopicDestination
content-type: application/json
accept: application/json

{
  "meta": {
    "profile": [
      "http://aidbox.app/StructureDefinition/aidboxtopicdestination-kafka-at-least-once"
    ]
  },
  "kind": "kafka-at-least-once",
  "id": "kafka-destination",
  "topic": "http://example.org/FHIR/R5/SubscriptionTopic/QuestionnaireResponse-topic",
  "parameter": [
    {
      "name": "kafkaTopic",
      "valueString": "aidbox-forms"
    },
    {
      "name": "bootstrapServers",
      "valueString": "kafka:29092"
    }
  ]
}

Step 3: Demonstration of QuestionnaireResponse subscriptions

Submit Form

Open the list of forms, click share -> enable ‘allow amend’ -> click attach -> copy the link -> open the link -> fill out the form, and submit it.

Check AidboxTopicDestination Status

Open the Aidbox REST Console and get the AidboxTopicDestination status:

GET /fhir/AidboxTopicDestination/kafka-destination/$status

See Messages in Kafka UI

Open Kafka UI -> Topics -> aidbox-forms -> messages and review the QuestionnaireResponse that was created after submitting the form.

Step 4: Set Up Subscription and Destination For Encounters

Create AidboxSubscriptionTopic Resource

To create a subscription on the Encounter resource for patients who have an identifier from the patient portal, open Aidbox UI -> APIs -> REST Console and execute the following request:

POST /fhir/AidboxSubscriptionTopic
content-type: application/json
accept: application/json

{
  "resourceType": "AidboxSubscriptionTopic",
  "url": "http://example.org/FHIR/R5/SubscriptionTopic/Encounter-topic",
  "status": "active",
  "trigger": [
    {
      "resource": "Encounter",
      "fhirPathCriteria": "subject.resolve().identifier.where(system.contains('patient-portal')).exists()"
    }
  ]
}

Create AidboxTopicDestination Resource

Create the AidboxTopicDestination for the Encounters.

POST /fhir/AidboxTopicDestination
content-type: application/json
accept: application/json

{
  "meta": {
    "profile": [
      "http://aidbox.app/StructureDefinition/aidboxtopicdestination-kafka-at-least-once"
    ]
  },
  "kind": "kafka-at-least-once",
  "id": "kafka-destination-encounters",
  "topic": "http://example.org/FHIR/R5/SubscriptionTopic/Encounter-topic",
  "parameter": [
    {
      "name": "kafkaTopic",
      "valueString": "aidbox-encounters"
    },
    {
      "name": "bootstrapServers",
      "valueString": "kafka:29092"
    }
  ]
}

Step 5: Demonstration of Encounter subscriptions

Create Patients and Encounters

Aidbox UI -> APIs -> REST Console

Create two Patients. First one is the user of the Patient Portal, it has the identifier in patient portal system:

POST /fhir/Patient
content-type: application/json
accept: application/json

{
  "resourceType": "Patient",
  "id": "patient-portal-example",
  "identifier": [
    {
      "use": "secondary",
      "type": {
        "coding": [
          {
            "system": "http://terminology.hl7.org/CodeSystem/v2-0203",
            "code": "PI",
            "display": "Patient Internal Identifier"
          }
        ]
      },
      "system": "http://hospital.example.org/patient-portal",
      "value": "portal_user_98765",
      "assigner": {
        "display": "Example Hospital Patient Portal"
      }
    }
  ]
}

Second one is not a portal user, so it doesn’t have a portal identifier:

POST /fhir/Patient
content-type: application/json
accept: application/json

{
  "resourceType": "Patient",
  "id": "patient-not-portal-example"
}

Create the Encounter for the first Patient:

POST /fhir/Encounter
content-type: application/json
accept: application/json

{
  "resourceType": "Encounter",
  "id": "encounter-001",
  "status": "finished",
  "class": {
    "system": "http://terminology.hl7.org/CodeSystem/v3-ActCode",
    "code": "AMB",
    "display": "ambulatory"
  },
  "subject": {
    "reference": "Patient/patient-portal-example"
  },
  "period": {
    "start": "2025-06-24T10:00:00Z",
    "end": "2025-06-24T10:30:00Z"
  }
}

Create the Encounter for the patient who is not a portal user:

POST /fhir/Encounter
content-type: application/json
accept: application/json

{
  "resourceType": "Encounter",
  "id": "encounter-002",
  "status": "in-progress",
  "class": {
    "system": "http://terminology.hl7.org/CodeSystem/v3-ActCode",
    "code": "EMER",
    "display": "emergency"
  },
  "subject": {
    "reference": "Patient/patient-not-portal-example"
  },
  "period": {
    "start": "2025-06-24T15:45:00Z"
  }
}

See Messages in Kafka UI

Open Kafka UI -> Topics -> aidbox-encounters -> messages and review the Encounter. You’ll notice that only the Encounter for the patient patient-portal-example was published. This behavior is due to the trigger configuration in the AidboxSubscriptionTopic:

"trigger": [
    {
      "resource": "Encounter",
      "fhirPathCriteria": "subject.resolve().identifier.where(system.contains('patient-portal')).exists()"
    }
  ]

Example of Kubernetes Setup

Also you can find example of k8s deployment:

Demo

A deployed and configured Aidbox instance with Kafka is available for you to explore how Aidbox’s SubscriptionTopic works. The SubscriptionTopic in Aidbox is set up to send QuestionnaireResponse events in the completed and amended status to Kafka.

To try it out:

  1. Open Aidbox Forms
  2. Share form, copy the link.
  3. Open the link and fill form.
  4. Open the Kafka UI to view your QuestionnaireResponse in the Kafka messages tab.