← All articles

Integrating with Epic: FHIR APIs, HL7v2 Feeds, and App Orchard in Practice

Last updated: November 2026

This is a companion to our 5 Hard Lessons from Building Epic FHIR Integrations, which covers App Orchard process, token management, and per-site deployment costs in depth. This page focuses on the technical integration patterns: what the data actually looks like on the wire, how to route it, and where the mapping gets tricky.

The three integration paths

Epic exposes data through three channels. Most production integrations use at least two.

ChannelProtocolTypical useAccess gate
FHIR R4 APIsREST/JSON over HTTPSOn-demand reads, patient-facing apps, Bulk ExportApp Orchard / Connection Hub, OAuth 2.0
HL7v2 feedsMLLP over TCPReal-time ADT, ORU, ORM, MDM eventsPoint-to-point with hospital interface team
CDS HooksREST/JSON (Epic calls you)Clinical decision support at point of careVendor Services membership + per-site activation

FHIR and HL7v2 are not interchangeable. FHIR gives you request/response semantics and patient-scoped queries. HL7v2 gives you push-based, event-driven feeds with sub-second latency on ADT events. If you need to know the moment a patient hits the ED, you want ADT over MLLP, not a FHIR poll.

ADT routing: the backbone

ADT (Admit/Discharge/Transfer) is the single most valuable feed from Epic. Every downstream system -- census boards, bed management, billing, care coordination -- keys off ADT events.

An Epic ADT^A01 (admit) looks like this on the wire:

MSH|^~\&|EPIC|HOSP|YOURAPP|YOURORG|20261101083000-0500||ADT^A01^ADT_A01|CTRL20261101001|P|2.5.1
EVN|A01|20261101083000-0500
PID|1||MRN98765^^^HOSP^MR~555-12-3456^^^SSA^SS||SMITH^JOHN^Q||19780315|M|||123 OAK ST^^CHICAGO^IL^60601
PV1|1|I|4EAST^412^A^HOSP^^^^N|E|||1234567890^JONES^SARAH^M^^^MD|||||||||VIP|VN20261101001^^^HOSP^VN|||||||||||||||||||||||||20261101083000-0500
DG1|1||I10^Essential hypertension^I10|||A
IN1|1|BCBS001|BCBS IL|BLUE CROSS BLUE SHIELD OF IL||||GROUP123

The key segments map to FHIR like this:

SegmentFHIR resource(s)Watch out for
PIDPatient (identifiers, demographics)PID-3 repeats -- each CX is a separate identifier. MRN system OID is site-specific.
PV1Encounter (class, location, participants, period)PV1-2 I = inpatient, O = ambulatory, E = emergency. PV1-19 is the visit number -- key your Encounter on this, not a generated ID.
DG1Condition (encounter diagnosis)DG1-3 carries the ICD-10 code. Map to Condition.code with system = http://hl7.org/fhir/sid/icd-10-cm.
IN1Coverage + OrganizationIN1-3 is the payer ID, IN1-4 is the payer name. You need an Organization resource for the payer.
EVNProvenance / Encounter.statusEVN-2 is the recorded datetime. Use this for sequencing, not MSH-7.

The ADT event types you will actually see from Epic in production:

TriggerMeaningWhat to do
A01AdmitCreate Encounter (status: in-progress)
A02TransferUpdate Encounter.location
A03DischargeClose Encounter (status: finished), set PV1-45 as period.end
A04Registration (outpatient)Create Encounter (class: AMB)
A08Update patient infoUpdate Patient demographics
A11Cancel admitReverse the A01 -- set Encounter status to cancelled
A13Cancel dischargeReverse the A03 -- reopen the Encounter
A40Patient mergePatient.link with type replaced-by. Then re-point every reference from the old Patient.

A08 is the highest-volume ADT event at most Epic sites. It fires on any demographic update -- address change, insurance update, phone number correction. If you are building a pipeline, handle A08 idempotently or you will drown in duplicate processing.

See our HL7v2 to FHIR migration guide for the full PID-to-Patient and PV1-to-Encounter field-level mapping tables.

ORU processing: lab results and beyond

ORU^R01 is Epic's results channel. Labs are the obvious case, but ORU also carries vitals, microbiology, pathology, and radiology results depending on site configuration.

The structure is hierarchical: one OBR (the order/report) contains multiple OBX segments (individual result values). The mapping:

  • OBR becomes a DiagnosticReport. OBR-4 is the report code (usually LOINC). OBR-7 is the observation datetime.
  • OBX becomes an Observation. OBX-3 is the test code, OBX-5 is the value, OBX-6 is the unit, OBX-8 is the abnormal flag.
  • OBX-4 (sub-ID) groups components. Two OBX lines with the same OBX-4 value collapse into one Observation with multiple component entries. Blood pressure (systolic + diastolic) is the classic example.

Epic-specific ORU patterns to watch for:

  • Preliminary vs. final results. OBX-11 P means preliminary, F means final, C means corrected. Your pipeline needs to handle updates to previously-received results, not just inserts.
  • Micro results are deeply nested. A microbiology culture can have organism-level OBR groups with susceptibility OBX segments underneath. The OBR-26 parent result field links them. This is where naive "one OBR = one report" logic breaks.
  • Radiology narratives. Radiology ORU often carries the report as a single OBX with value type TX or FT (formatted text). The structured data is minimal; the narrative is what clinicians read.

CDS Hooks

Epic supports CDS Hooks at several workflow points: patient-view (chart open), order-select, order-sign, and encounter-discharge. Your service registers an endpoint, and Epic POSTs a JSON payload with the patient context when the hook fires.

The practical constraints:

  • Latency budget is tight. Epic expects a response within 10 seconds. If your CDS service calls an ML model or external API, you need aggressive caching or pre-computation. A timeout returns no card to the clinician.
  • Cards must be actionable. Epic renders your response as "cards" in the EHR. Informational cards get ignored. Cards with specific suggested actions (a link to a form, a suggested order) get clicks. Design for the click.
  • Requires Vendor Services. CDS Hooks access is a proprietary API, not part of the open FHIR R4 surface. You need an Epic Vendor Services agreement, which comes with fees and a longer review cycle. See the timeline table in our App Orchard deep-dive.

For a broader CDS Hooks reference beyond Epic, see our CDS Hooks guide.

Bulk Export for population health

Epic's FHIR $export operation (the Bulk Data Access spec) lets you pull population-level data asynchronously. The flow:

  1. POST to $export with resource types and optional filters.
  2. Poll the status endpoint until 200 OK.
  3. Download NDJSON files (one per resource type) from the returned URLs.

What's actually useful here and what's not:

  • Good for: Quality measure calculation, cohort identification, analytics, population health dashboards. You get current-state snapshots of Patient, Condition, Observation, MedicationRequest across a patient population.
  • Not good for: Real-time workflows, resource history, or anything that needs sub-day freshness. Export jobs can take hours at large sites. The output is current state only -- no version history, no deleted resources.
  • Filtering is limited. The _typeFilter parameter lets you narrow by resource type, but granular date-range or condition-based filtering varies by Epic version. You often end up downloading more data than you need and filtering client-side.

What about MyChart?

MyChart is Epic's patient portal. If you are building a patient-facing app, you use the same FHIR R4 APIs with a patient-scoped access token (SMART on FHIR launch/patient scope). The patient authenticates via MyChart, and you get a token scoped to their record. The API surface is a subset of what's available to provider-facing apps -- you will not see notes, some orders, or anything the health system has restricted from patient view.

Choosing your integration path

There is no single right answer. Here is how we have seen it break down:

Your needRecommended pathWhy
Real-time patient movementHL7v2 ADT over MLLPSub-second latency, no polling overhead
On-demand clinical data pullFHIR R4 (patient-scoped)Clean REST semantics, well-documented
Point-of-care alertsCDS HooksEmbedded in clinician workflow
Population analyticsFHIR Bulk ExportDesigned for batch extraction
Document exchangeFHIR DocumentReference or HL7v2 MDMDepends on whether you need push or pull
Bidirectional clinical writesFHIR R4 + Vendor ServicesWrite scopes require additional review

Most teams building something nontrivial end up with FHIR for reads and HL7v2 for real-time events. The two complement each other. Tools like MedHook, Mirth Connect, or Rhapsody can bridge both channels into a unified pipeline -- the choice depends on your team's skill set and scale requirements. See our integration engine comparison for the trade-offs.


If you are planning an Epic integration and want a second opinion on the architecture -- which channels to use, how to handle the App Orchard timeline, or how to structure your HL7v2-to-FHIR transform -- we are happy to talk it through.