Syncing patient data between Medplum and Epic using FHIR
Integrating with Epic is usually not where healthcare teams want to spend their product energy.
The product goal might be simple: keep patient data in sync, pull the latest clinical context, or make sure a care team has the right allergies and medications before acting. But before any of that works, developers need to deal with authentication, sandbox setup, FHIR resource permissions, identifiers, and the mechanics of moving data between systems.
That is why we extended Medplum’s Epic demo bot. The bot shows a practical pattern for connecting Medplum to Epic through FHIR APIs, using a Medplum Bot as the backend integration layer.
What the Epic integration bot does
The demo bot runs with a Medplum Patient resource as input. From there, it checks whether that patient already has an identifier that links the Medplum record to an Epic patient record.
If the identifier does not exist, the bot creates a new Patient record in Epic using the data available in Medplum. After that, it updates the Medplum patient with the Epic identifier, so future syncs know which external record to reference.
If the identifier already exists, the bot uses it to query Epic for the latest patient information and related clinical data. In this version, the sync includes key patient profile resources such as demographics, allergies, and medications.
This keeps the example small enough to understand, while still showing the core workflow most teams need when building real healthcare integrations: create or match the patient, store the external identifier, fetch updated FHIR resources, and keep both systems aligned.
Why use a Medplum Bot for this?
Medplum Bots are useful for integration workflows because they can run server-side logic close to the FHIR data model.
In this example, the bot is a backend system application: that means it connects directly to the Epic FHIR server without a user login flow, using backend JWT authentication instead.
Many healthcare integrations do not start with a user clicking “connect Epic” from an interface: they happen in the background, triggered by events, scheduled jobs, or operational workflows. A patient is created. A record needs to be enriched. A downstream system needs updated medication data. A care workflow depends on information that lives in another EHR.
For those cases, a bot can act as the orchestration layer between Medplum and the external FHIR server.
What changed in this contribution
This contribution focused on two improvements.
First, we expanded the synchronization logic beyond the basic patient record. The bot now handles additional patient profile data, making the example more useful for developers who want to understand how patient history can be assembled across systems.
Second, we improved the setup instructions for the Epic sandbox environment. That includes guidance on creating a backend service app, selecting the right FHIR R4 APIs, configuring public/private keys, and adding the required Medplum Bot Secrets.
Those details are easy to underestimate. Most integration examples fail because the setup path is unclear: which audience should the app use, which resources need to be enabled, where does the client ID come from, how should the private key be formatted, and why is the API still failing after permissions were changed?
Making that setup path explicit lowers the barrier for other developers to test the integration and adapt the pattern to their own workflows.
What developers should pay attention to
There are a few implementation details worth calling out.
The bot depends on the Epic identifier stored on the Medplum patient. Without it, the bot treats the patient as new to Epic and creates a record. With it, the bot can fetch and sync the existing external record.
The Epic app needs to be configured as a backend service application. That is what allows server-to-server authentication using JWT client assertion, instead of relying on an interactive user authentication flow.
The Epic sandbox app also needs the right incoming APIs enabled. For this demo, that includes Patient, AllergyIntolerance, MedicationRequest, Medication, and related resources such as Organization and Practitioner.
The private key formatting matters as well. When stored as a Medplum Bot Secret, the key needs to preserve the expected PEM structure, including newline characters. A small formatting issue can be enough to break authentication.
Finally, API permission changes in Epic may take time to propagate. If a newly enabled resource still fails right after configuration, waiting and retrying may be part of the debugging process.
Why this matters
This is a working reference for a common interoperability pattern: use Medplum as the FHIR-native application layer, connect to an external EHR through FHIR APIs, and sync the patient context needed by the product.
For teams building healthcare software, that pattern can show up in many forms: a specialty care platform pulling medication data, a patient-facing app displaying consolidated history, a care coordination tool checking allergies before a workflow starts, or an internal system reconciling patient demographics across tools.
The value of a demo like this is that it makes the integration concrete. Instead of starting from a blank file, developers can see the authentication model, the resource flow, the identifier strategy, and the sandbox configuration in one place.
That is often the first step toward turning interoperability from an abstract requirement into something a team can actually build, test, and improve.

.webp)

%20(1).webp)


%201.webp)
