FHIR Data Synchronization Case Study: Connecting Hospitals to a Central Tumor Board
CTO & Co-Founder
CTO & Co-Founder at Nirmitee.io. Architects healthcare integrations across FHIR, SMART on FHIR, ABDM and NHCX, writing from production experience taking hospital software from sandbox to go-live.
At a glance
- Client: a European regional cancer care network whose member hospitals share one health data platform.
- Need: move tumor board cases from each hospital's FHIR instance to one central instance, and return the board's structured results to every hospital that took part.
- What Nirmitee built: a FHIR R4 transfer service, written to the platform team's requirements, that runs scheduled synchronization workflows in both directions.
- Data in scope: Patient and Encounter from hospital to centre; QuestionnaireResponse from centre to hospital.
- Stack: Java 17, Spring Boot, Temporal, FHIR R4 REST, OAuth 2.0, Kubernetes, Helm, Prometheus, Grafana.
- Period: June to November 2025.
- Status at end of engagement: deployed by the platform team to its sandbox and integration environments for testing. This case study does not cover a production go-live.
Summary
This FHIR data synchronization case study describes how Nirmitee engineers built the transfer service behind a regional cancer care network's central tumor board. The board needed one combined view of each case, and each hospital needed the board's decision back in its own record.
Working to requirements written by the network's platform team, Nirmitee wrote a Java workflow service that picks up flagged cases from each hospital's FHIR instance, matches the patient in the central instance by a shared identifier, applies clear ownership rules, and routes each board result back to the hospital it came from. The service was delivered as a container image with a Helm chart for the platform team's Kubernetes clusters. In September 2025 the platform team reported it running without errors in its sandbox, with its workflows starting.
The decisions that shaped the service, and the defects that testing exposed, are set out below. Nirmitee provides this kind of work through its FHIR integration services.
The business challenge
A tumor board is a meeting where oncologists, surgeons, radiologists and pathologists agree a treatment plan for one patient. In this network the board runs centrally, but the patient's data lives in the member hospitals. The same patient can be treated at two hospitals, each with its own patient number and its own encounter number, and there is no network-wide case number.
The platform team's requirements set out the job. A clinician marks a case for the tumor board in the hospital information system. That marker reaches the hospital's FHIR instance as an active Flag resource with a configured code. From that point the service must copy the Patient and Encounter to the central instance, attach a second hospital's data to the same central patient and encounter, and send the board's results, held centrally as QuestionnaireResponse resources, back to every hospital that took part.
The requirements also set service levels. New or changed data had to be picked up at least every 15 minutes. Transfers had to be encrypted in transit and logged, and the service had to expose Prometheus monitoring endpoints. The platform team expected at most 10 to 20 patients a day across the first hospitals connected, so correctness mattered far more than throughput.
What was at stake
A synchronization service for a tumor board fails quietly when it fails. Nothing crashes. A patient is simply missing from the board's list, or a result arrives at the wrong hospital, and a clinician is the first person to notice.
- Patient safety. Matching the wrong patient across hospitals would put one person's findings into another person's record. The matching rule had to be strict enough that a doubtful case is not transferred at all.
- Trust between hospitals. Two hospitals writing to one central record need to know which of them may change it. Without an agreed rule, demographics are overwritten and disputes follow.
- Closing the loop. A board decision is only useful once it is back in the treating hospital's record. The central record therefore had to remember where each case came from.
- Fit with the platform. The network's platform team runs every component on its own Kubernetes clusters, to its own delivery standard. A service that did not fit that standard could not be deployed, however well it worked elsewhere.
Nirmitee's buyer guide to FHIR data synchronization between hospitals sets out these decisions in general terms. This page shows how they were settled in one real network.
The approach
The build followed five principles. Most were set by the platform team's requirements, and the rest came out of testing.
- Share a defined cohort, not everything. Only patients flagged for the tumor board move. The trigger is a clinician action in the hospital's own system, which gives every transfer a clear, auditable reason.
- Match deterministically or not at all. Patients are matched only by a shared national insurance identifier that must be present on every Patient resource. The service checks for it and does not transfer a patient without it. Nirmitee's guide to patient matching beyond demographics covers the options for networks that lack such an identifier.
- One direction per resource type. Patient and Encounter flow only from hospital to centre. QuestionnaireResponse flows only from centre to hospital. This removes most conflict handling before it starts.
- Clear ownership of shared records. The hospital that opens the case is the leading instance and alone may update the central Patient and Encounter. A second hospital may add its own identifiers and nothing else.
- Durable, observable execution. Every run is a workflow with its own history, retries and metrics, so a missing patient shows up on a dashboard, not as a question from a clinician.
The work drew on Nirmitee's healthcare product engineering practice: engineers writing production services to a client's own platform standards, with the client's engineers testing every release.
What was delivered
Each capability below is backed by the delivered code, its documentation or the platform team's own test reports.
- Flag-triggered transfer from hospital to centre. A scheduled workflow per hospital pages through active tumor board Flags, loads the linked Patient and Encounter, checks the shared identifier, and creates or updates the central records. Proof: the platform team's test logs show the workflow paging through Flags and fetching patients in its sandbox.
- Result routing from centre to hospital. A second scheduled workflow fetches new or changed QuestionnaireResponses, reads the originating hospital and local resource ID from an identifier on the resource, and creates or updates the local copy only when its content has changed. Proof: merged in November 2025 and documented in the architecture document.
- Ownership and conflict rules in code. Leading-instance updates, secondary identifiers for a second hospital, and handling for two hospitals creating the same patient at the same moment. Proof: the conflict fix was tested and merged in late November 2025.
- Audit trail. AuditEvent resources record completed transfers. Proof: merged with the back-transfer in November 2025.
- Monitoring. Prometheus metrics for workflow starts, completions, failures and durations, activity errors, and transfers by resource type and direction, with scrape configuration and a Grafana dashboard. This follows the practice Nirmitee applies in integration monitoring and support.
- Platform-ready packaging. A container image built by the platform team's own pipeline, and a Helm chart with configurable schedules, identifier systems, Temporal connection, encryption settings and externally mounted per-hospital configuration.
- Documentation for the operators. An architecture document, a deployment guide, and a storage and backup note for the platform team.
How it works
The service is a single Spring Boot application that registers its workflows and activities with a Temporal server. Temporal runs the schedules, retries failed steps and keeps a history of every run. The service has no database of its own. Workflow inputs and results are held in Temporal's history, so an optional codec can encrypt those payloads, switched on through the Helm values. See Nirmitee's article on durable execution with Temporal in healthcare.
The service reads and writes only through each instance's FHIR API. Early in the project the design also read a second, non-FHIR data store on the platform; in July 2025 the platform team decided the service should use the FHIR API alone, and that path was dropped. Because it has API access only, the service detects changes by scheduled polling on each resource's last-updated time rather than by database change capture. Nirmitee's article on change data capture for EHR sync covers the other route.
| Resource | Direction | What starts it | Rule applied |
|---|---|---|---|
| Patient | Hospital to centre | Active tumor board Flag in the hospital | Shared insurance identifier required; leading hospital owns the record; a second hospital adds its identifiers only |
| Encounter | Hospital to centre | The same Flag | Created against the central patient; matched across all of its identifiers |
| QuestionnaireResponse | Centre to hospital | New or changed board result in the central instance | Routed by an identifier that carries the originating hospital and local ID; written only when content has changed |
Hospital to centre. For each configured hospital, the workflow obtains a token using the OAuth 2.0 client credentials grant, pages through active tumor board Flags, and loads the referenced Patient and Encounter. If the shared insurance identifier is missing, the patient is not transferred. Otherwise the workflow searches the central instance for that identifier. If no central patient exists, it creates one. If one exists, the leading hospital may update it, and a second hospital adds its own identifier as a secondary identifier. Encounters are created against the central patient and matched on every identifier they carry, because hospitals share encounter numbers by an agreed manual step rather than through a common case number.
Centre to hospital. The return workflow fetches new or changed QuestionnaireResponses from the central instance. Each carries an identifier that records which hospital the case came from and its local resource ID. The workflow obtains that hospital's token, compares the result with the local copy, and creates or updates it only when something has changed.
Lessons that transfer to your project
Several of these lessons come from defects that the platform team's engineers found in testing and that Nirmitee then fixed.
1. Generate workflow IDs from the workflow clock, not the system clock
After a service restart, scheduled workflows failed and needed manual termination. The cause was a workflow ID built from the system time inside workflow code. Temporal replays a workflow's history after a restart, and a value that differs on replay breaks it. Using Temporal's deterministic workflow time fixed the problem. Any value computed inside workflow code must come out the same on replay.
2. Make polling windows overlap the schedule
In testing, a patient created in a hospital was never transferred. Each run fetched records from a two-minute window, but runs started three minutes apart, and the record's last-updated time fell into the one-minute gap. Widening the window fixed the symptom. The lasting fix is a window that overlaps the schedule interval, combined with create-or-update logic that is safe to repeat.
3. Decide who wins a simultaneous create before it happens
When two hospitals flagged the same patient at nearly the same moment, both tried to create the central record. The rule implemented: the first creator becomes the leading instance, and the second receives HTTP 409 Conflict, reads the current central record and adds only its own identifiers. Write this rule down with the ownership model, before the first duplicate appears.
4. Check search parameters against the specification, and do not retry client errors
An early back-transfer query used _questionnaire, which is not a valid search parameter for QuestionnaireResponse; the correct one is questionnaire. The platform team's testing caught it. In the same release, a malformed query URL returned HTTP 405, which the service treated as retryable and kept retrying. Validate each parameter against the resource definition or the server's CapabilityStatement, remember that the FHIR search specification lets servers ignore unknown parameters, and treat 4xx responses as failures, not retries.
5. Put limits on retries around authentication
When a token request failed, or a hospital's instance was unavailable, an early version of the workflow looped and never finished. The fix handled both cases so that the task terminates instead of looping. Durable execution is valuable, but it does not decide on its own when to stop.
6. Match identifiers on the full system URI
Matching the insurance identifier by part of its system name worked on test data but is not safe, because another system can contain the same text. At the platform team's request the service moved to the full system URI, held in configuration rather than code.
7. Test in a copy of the client's environment, and gate every release
Some early failures in the platform team's sandbox were configuration differences: a placeholder that did not resolve, a file expected on a mounted volume, a setting present in one values file and not the other. Others were code defects in a release that should not have reached a shared environment. A local cluster that mirrors the client's Helm values and secret layout, a documented list of every configuration key, and a release gate that runs the client's example resources would each have caught these earlier. The same checks belong in the release process of any service that others run, including one kept current under software maintenance and support.
Where this applies
The pattern fits any hub-and-spoke exchange where a clinical action selects the patients to share, a central instance holds the combined record, and results must return to the right member. These are applications of the pattern, not work delivered for this client.
- Multi-hospital oncology programs in the US and elsewhere that run a shared tumor board across sites and need case data in one place without merging the hospitals' records.
- Health information exchanges and clinically integrated networks that move a defined subset of FHIR resources between member servers and a hub, part of a wider healthcare interoperability estate.
- Registries and care programs where an EHR flag, not a bulk extract, decides which patients are shared. Where the source is an EHR without a usable FHIR API, the same rules apply on top of EHR and EMR integration.
For a different kind of data movement, a one-time move out of a legacy system, see the EHR data migration case study for a North American home health care provider.
For your engineering team
- Runtime: Java 17, Spring Boot, Temporal workflows and activities; no application database; optional payload encryption codec.
- Standards: HL7 FHIR R4 REST (Flag, Patient, Encounter, QuestionnaireResponse, AuditEvent); OAuth 2.0 client credentials per hospital; tumor board marker arriving from the hospital system over HL7 v2.
- Matching: deterministic, on a shared insurance identifier with the full system URI; encounters matched across all identifiers; 409 Conflict handling for simultaneous creates.
- Change detection: scheduled polling on last-updated time; the requirement was at least every 15 minutes.
- Deployment: container image built by the client's pipeline; Helm chart for Kubernetes; configuration and secrets mounted from outside the image.
- Operations: Prometheus metrics and scrape configuration, Grafana dashboard, AuditEvent per completed transfer, architecture and deployment documentation.
Working with Nirmitee
Nirmitee is ISO 27001:2022 certified, is HIPAA-enabled and signs BAAs. It builds FHIR services that run on a client's own platform, to that client's delivery standards, and it works best where the client's engineers test every release, as they did here.
A first conversation is a scoping call. Bring the list of sites, the identifiers each one records, the resources that must move and in which direction, and where the service will run. Nirmitee will set out the ownership, matching and failure rules the build needs before any code is written. Book a scoping call.


