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.
| Channel | Protocol | Typical use | Access gate |
|---|---|---|---|
| FHIR R4 APIs | REST/JSON over HTTPS | On-demand reads, patient-facing apps, Bulk Export | App Orchard / Connection Hub, OAuth 2.0 |
| HL7v2 feeds | MLLP over TCP | Real-time ADT, ORU, ORM, MDM events | Point-to-point with hospital interface team |
| CDS Hooks | REST/JSON (Epic calls you) | Clinical decision support at point of care | Vendor 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:
| Segment | FHIR resource(s) | Watch out for |
|---|---|---|
| PID | Patient (identifiers, demographics) | PID-3 repeats -- each CX is a separate identifier. MRN system OID is site-specific. |
| PV1 | Encounter (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. |
| DG1 | Condition (encounter diagnosis) | DG1-3 carries the ICD-10 code. Map to Condition.code with system = http://hl7.org/fhir/sid/icd-10-cm. |
| IN1 | Coverage + Organization | IN1-3 is the payer ID, IN1-4 is the payer name. You need an Organization resource for the payer. |
| EVN | Provenance / Encounter.status | EVN-2 is the recorded datetime. Use this for sequencing, not MSH-7. |
The ADT event types you will actually see from Epic in production:
| Trigger | Meaning | What to do |
|---|---|---|
| A01 | Admit | Create Encounter (status: in-progress) |
| A02 | Transfer | Update Encounter.location |
| A03 | Discharge | Close Encounter (status: finished), set PV1-45 as period.end |
| A04 | Registration (outpatient) | Create Encounter (class: AMB) |
| A08 | Update patient info | Update Patient demographics |
| A11 | Cancel admit | Reverse the A01 -- set Encounter status to cancelled |
| A13 | Cancel discharge | Reverse the A03 -- reopen the Encounter |
| A40 | Patient merge | Patient.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
componententries. Blood pressure (systolic + diastolic) is the classic example.
Epic-specific ORU patterns to watch for:
- Preliminary vs. final results. OBX-11
Pmeans preliminary,Fmeans final,Cmeans 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
TXorFT(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:
POSTto$exportwith resource types and optional filters.- Poll the status endpoint until
200 OK. - 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
_typeFilterparameter 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 need | Recommended path | Why |
|---|---|---|
| Real-time patient movement | HL7v2 ADT over MLLP | Sub-second latency, no polling overhead |
| On-demand clinical data pull | FHIR R4 (patient-scoped) | Clean REST semantics, well-documented |
| Point-of-care alerts | CDS Hooks | Embedded in clinician workflow |
| Population analytics | FHIR Bulk Export | Designed for batch extraction |
| Document exchange | FHIR DocumentReference or HL7v2 MDM | Depends on whether you need push or pull |
| Bidirectional clinical writes | FHIR R4 + Vendor Services | Write 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.