Introduction
HL7 v2.x has been the backbone of healthcare system integration for over three decades. Despite the rise of FHIR, the vast majority of production healthcare environments — PACS, RIS, LIS, EMR, pharmacy, and billing systems — still communicate exclusively over HL7 v2 pipes. If you work in healthcare IT, you will spend significant time on these interfaces regardless of where the industry is headed.
This guide is not a standards document. It is a practitioner's reference built from the reality of configuring, debugging, and maintaining HL7 interfaces in live clinical environments. We cover message anatomy, message types, interface engine configuration, testing methodology, and the most common failure patterns you will encounter in production.
1. HL7 v2.x Architecture
The Messaging Model
HL7 v2 is a messaging standard, not an API. Messages are discrete, self-contained units of clinical information transmitted from one system (the sending application) to another (the receiving application) over a transport connection. The standard defines:
- Message structure — which segments appear, in what order, and whether they are required or optional
- Data types — how individual fields are encoded (strings, dates, coded values, etc.)
- Message triggers — the clinical events that cause a message to be generated
- Acknowledgement protocol — how the receiver confirms receipt and processing
The underlying transport is almost always MLLP (Minimal Lower Layer Protocol), a thin TCP/IP wrapper that frames HL7 messages with start-of-block (0x0B) and end-of-block (0x1C 0x0D) characters. MLLP is synchronous — the sender transmits a message and waits for an ACK before sending the next.
Encoding Rules
Every HL7 v2 message uses four special characters defined in MSH-1 and MSH-2:
| MSH Field | Default Value | Purpose |
|---|---|---|
| MSH-1 | | (pipe) | Field separator |
| MSH-2, char 1 | ^ (caret) | Component separator |
| MSH-2, char 2 | ~ (tilde) | Repetition separator |
| MSH-2, char 3 | \ (backslash) | Escape character |
| MSH-2, char 4 | & (ampersand) | Subcomponent separator |
These characters must not appear unescaped in data. Patient names containing ^ or ampersand characters in free-text fields are among the most common sources of parsing failures. Always validate field data against the encoding character set before transmission.
Message Hierarchy
Message
└── Segment (identified by 3-character code: MSH, PID, PV1...)
└── Field (separated by |)
└── Component (separated by ^)
└── Subcomponent (separated by &)
Repetitions of a field are separated by ~. For example, a patient with two phone numbers:
PID|||12345||SMITH^JOHN||19800101|M|||123 MAIN ST^^BOSTON^MA^02101||555-1234~555-5678
2. Message Structure: Segment by Segment
MSH — Message Header
The first and mandatory segment in every HL7 message. Defines encoding characters, sending/receiving application identities, message type, and timestamp.
Critical fields:
| Field | Name | Why It Matters |
|---|---|---|
| MSH-3 | Sending Application | Used by receiving system to identify message source and apply routing rules |
| MSH-4 | Sending Facility | Site-level identity — critical in multi-site deployments |
| MSH-5 | Receiving Application | Determines the target system for routing |
| MSH-6 | Receiving Facility | Target facility — must be configured on the receiving end |
| MSH-7 | Date/Time of Message | Used for deduplication and message ordering |
| MSH-9 | Message Type | ADT^A01, ORM^O01 etc. — the single most important routing field |
| MSH-10 | Message Control ID | Unique per-message ID used for ACK correlation |
| MSH-11 | Processing ID | P (Production), T (Test), D (Debug) — never send T to a production system |
| MSH-12 | Version ID | 2.3, 2.3.1, 2.4, 2.5, 2.5.1 — must be compatible with receiving system |
MSH-3 through MSH-6 are the routing quadrant. Every interface engine route is configured against these four fields. Misconfigured values here mean messages arrive at the wrong destination or are rejected with "Unknown Sending Application" errors.
PID — Patient Identification
Contains all patient demographic information. Fields 3, 5, 7, and 8 are the core patient record.
Critical fields:
| Field | Name | Notes |
|---|---|---|
| PID-3 | Patient Identifier List | CX data type — can carry multiple IDs (MRN, NHS, SSN) with assigning authority |
| PID-5 | Patient Name | XPN data type — Family^Given^Middle^Suffix^Prefix |
| PID-7 | Date/Time of Birth | Used for patient matching and age computation |
| PID-8 | Administrative Sex | M, F, O, U — fed into DICOM Patient Sex (0010,0040) |
| PID-11 | Patient Address | XAD data type — street^other^city^state^zip^country |
| PID-18 | Patient Account Number | Billing account — distinct from Medical Record Number |
PID-3 is the most complex field. It carries the patient identifier with its assigning authority:
PID|||123456^^^CITYHOSP^MR||SMITH^JOHN^A
Here 123456 is the MRN, CITYHOSP is the assigning facility, and MR is the identifier type (Medical Record number). In multi-site environments, always include the assigning authority to prevent cross-site patient ID collisions.
PV1 — Patient Visit
Describes the patient's current visit or encounter context.
Critical fields:
| Field | Name | Notes |
|---|---|---|
| PV1-2 | Patient Class | I (Inpatient), O (Outpatient), E (Emergency), R (Recurring) |
| PV1-3 | Assigned Patient Location | Point of care^Room^Bed^Facility — feeds DICOM worklist location |
| PV1-8 | Referring Doctor | XCN data type — ID^LastName^FirstName |
| PV1-19 | Visit Number | Encounter ID — distinct from account and MRN |
| PV1-44 | Admit Date/Time | Start of this visit |
PV1-3 (location) is particularly important for imaging: many PACS and RIS systems use inpatient location to drive priority routing and display order in emergency worklists.
OBR — Observation Request
The order segment for diagnostic services. Used in ORM (orders) and ORU (results) messages.
Critical fields:
| Field | Name | Notes |
|---|---|---|
| OBR-2 | Placer Order Number | Generated by the ordering system (HIS/EMR) |
| OBR-3 | Filler Order Number | Generated by the performing system (RIS/LIS) |
| OBR-4 | Universal Service ID | Ordered procedure code (CPT, local) ^procedure description |
| OBR-7 | Observation Date/Time | When the observation/exam was performed |
| OBR-16 | Ordering Provider | Physician who ordered the test |
| OBR-22 | Results Report/Status Change Date/Time | When status last changed |
| OBR-25 | Result Status | P (Preliminary), F (Final), C (Corrected), X (Cancelled) |
OBR-25 (Result Status) drives downstream workflows. A Final (F) result triggers report availability notifications; a Corrected (C) result should trigger re-notification. Many integration failures trace to wrong or missing Result Status values.
3. The Eight Core Message Types
ADT — Admission, Discharge, Transfer
ADT messages are the lifeblood of patient flow integration. They update the patient index across all connected systems. The trigger event suffix (A01, A02...) specifies the exact clinical event.
| Trigger | Event | Primary Use |
|---|---|---|
| A01 | Admit/Visit Notification | Patient admitted — creates new visit in PACS/RIS worklist |
| A02 | Transfer a Patient | Patient moved between wards — updates location in PACS |
| A03 | Discharge/End Visit | Patient discharged — closes encounter |
| A04 | Register a Patient | Outpatient registration |
| A05 | Pre-Admit a Patient | Pre-admission registration |
| A08 | Update Patient Information | Demographic update — name change, DOB correction |
| A11 | Cancel Admit | Reverses an A01 |
| A12 | Cancel Transfer | Reverses an A02 |
| A13 | Cancel Discharge | Reverses an A03 |
| A28 | Add Person Information | New patient creation without a visit |
| A31 | Update Person Information | Patient demographic update without a visit |
| A40 | Merge Patient - Patient Identifier List | Patient merge — critical for identity management |
The A40 Merge is the most operationally dangerous ADT message. An A40 notifies receiving systems that two patient records should be merged into one. If the receiving system doesn't correctly handle A40, you end up with duplicate patient records, split imaging histories, and potential patient safety events. Test A40 handling explicitly during integration testing.
ORM — Order Message
ORM^O01 messages carry procedure orders from an ordering system (HIS/EMR) to a performing department (RIS, LIS).
A typical radiology ORM flow:
EMR → ORM^O01 (new order) → RIS → ACK
RIS → ORM^O01 (order accepted) → PACS MWL SCP
Key ORM fields beyond the PID/PV1/OBR triad:
- ORC-1 (Order Control) —
NW(New),CA(Cancel),DC(Discontinue),OE(Order/Service Error),RP(Replace). Every ORM must have an ORC segment with this field populated. - ORC-2 (Placer Order Number) — Must be globally unique. This is the primary key linking all downstream events to the original order.
- ORC-5 (Order Status) —
IP(In Process),CM(Complete),CA(Cancelled).
ORU — Observation Result
ORU^R01 carries diagnostic results back from the performing system to the ordering system and EMR.
In radiology: the RIS sends an ORU^R01 when a report is signed, carrying the report text in OBX segments. In the lab: the LIS sends ORU^R01 for every resulted test.
OBX segments carry the actual results:
| Field | Name | Notes |
|---|---|---|
| OBX-2 | Value Type | TX (text), NM (numeric), CE (coded element), ED (encapsulated data) |
| OBX-3 | Observation Identifier | LOINC code^local description^coding system |
| OBX-5 | Observation Value | The actual result value |
| OBX-6 | Units | UCUM units for numeric results |
| OBX-8 | Abnormal Flags | H (High), L (Low), A (Abnormal), C (Critical) |
| OBX-11 | Observation Result Status | F (Final), P (Preliminary), C (Corrected) |
For radiology reports, OBX-2 is typically TX for free text or ED for PDF-encoded reports. For lab, each analyte gets its own OBX segment with numeric values and UCUM units.
SIU — Scheduling Information Unsolicited
SIU messages carry scheduling events from a scheduling system (typically RIS) to other consumers (EMR, patient portals).
| Trigger | Event |
|---|---|
| S12 | Notification of New Appointment Booking |
| S13 | Notification of Appointment Rescheduling |
| S14 | Notification of Appointment Modification |
| S15 | Notification of Appointment Cancellation |
| S17 | Notification of Appointment Deletion |
SIU feeds the patient portal, appointment reminder systems, and transport booking. Missing or delayed SIU messages cause patient portal appointment lists to be out of date.
MDM — Medical Document Management
MDM^T02 is used to transmit clinical documents — typically radiology reports, ECG interpretations, or surgical notes — as HL7 messages. The document content is embedded in OBX segments, usually as Base64-encoded PDF (OBX-2 = ED).
DFT — Detailed Financial Transaction
DFT^P03 messages carry charge/billing events from performing departments to the billing system. In radiology, a DFT is sent when an exam is completed to trigger the CPT billing workflow. Missing DFT messages cause revenue leakage.
MFN — Master File Notification
MFN messages synchronise master data (provider directories, procedure catalogues, location tables) between systems. MFN^M02 updates the staff practitioner master file; MFN^M12 updates the master observation identifier table.
ACK — General Acknowledgement
Every HL7 message expects an ACK in response. The ACK carries:
- MSA-1 (Acknowledgement Code):
AA(Application Accept),AE(Application Error),AR(Application Reject) - MSA-3 (Text Message): Human-readable error description when AE or AR
AA means the receiving application processed the message successfully. AE means it received it but encountered an error processing it. AR means it rejected the message outright (unknown message type, security rejection, etc.). Anything other than AA must be investigated and resolved — do not silently discard non-AA ACKs.
4. Interface Engine Architecture
The Role of an Integration Engine
In any healthcare environment with more than two connected systems, point-to-point HL7 connections become unmanageable. The integration engine (Mirth Connect, Rhapsody, Ensemble/HealthShare, Iguana, Infor Cloverleaf) acts as a hub:
EMR ──┐
LIS ──┤──► Integration Engine ──► PACS
RIS ──┤ ──► Billing
ADT ──┘ ──► Portal
Each system connects to the engine rather than directly to every other system. The engine handles:
- Transport — MLLP listeners, HTTP/REST, file-based
- Routing — directing messages to the right destination(s) based on content
- Transformation — mapping fields between different system formats
- Acknowledgement — managing ACK flows between sender and engine, and engine and receiver
- Alerting — notifying engineers when queues back up or errors occur
Routing Logic
Most engines route on one or more of these fields:
- MSH-9 (Message Type + Trigger Event)
- MSH-3 (Sending Application)
- MSH-4 (Sending Facility)
- ORC-1 (Order Control code)
- PV1-2 (Patient Class)
A typical routing rule: "ADT^A08 from EPIC_PROD → fan out to RIS, PACS, PORTAL". Build routing rules from the most specific to the least specific, and always include a catch-all route that logs unmatched messages for review.
Transformation
Transformation is the most labour-intensive part of integration work. Common transformations include:
- Field mapping — moving a value from one field to another between sender and receiver schemas
- Code translation — converting local procedure codes to the receiving system's codes (e.g., local exam codes → CPT codes)
- Name normalisation — standardising PN component order or casing across systems
- Date format conversion — HL7 uses YYYYMMDDHHMMSS; some systems expect different formats
- Splitting and merging — splitting one message into multiple, or combining multiple into one
Always store transformation rules in version-controlled configuration, not embedded scripts. Document every transformation with the business reason, source field, target field, and any code mappings applied.
ACK Handling Modes
Integration engines support three ACK modes:
| Mode | Behaviour | Use When |
|---|---|---|
| Original | One ACK from final destination | Simple two-node connections |
| Enhanced | Commit ACK (transport receipt) + Application ACK (processing receipt) | Critical messages, billing, clinical alerts |
| Proxy | Engine forwards the destination's ACK back to the sender | When sender requires final-destination ACK |
For clinical integrations (ADT, orders, results), use Enhanced mode so you can distinguish between "message received by engine" and "message successfully processed by destination." Original mode masks processing failures at the destination.
5. Testing and Validation Methodology
Environment Strategy
Never test on production. Maintain at minimum three environments:
| Environment | Connected Systems | Purpose |
|---|---|---|
| Development | Simulators/stubs | Initial development and unit testing |
| Integration/SIT | System replicas with sanitised data | End-to-end scenario testing |
| UAT | Production-like with test data | Clinical acceptance testing |
For HL7 interfaces, system replicas must use the exact same software version as production — HL7 behaviour differences between major versions of the same product are common.
Building a Test Message Library
For each interface, build a library of test messages covering:
- Happy path — complete, valid message for each trigger event
- Mandatory field missing — one test per mandatory field
- Encoding edge cases — patient name with special characters, very long fields, multi-value repeating fields
- Merge scenarios — A40 merge with valid and invalid patient pairs
- Cancel/correction scenarios — ORM cancellation, ORU corrected result
- Boundary values — zero-length optional fields, maximum-length fields
Store these in your version control repository alongside the interface configuration. Replay them against every deployment.
MLLP Capture and Analysis
For production debugging, capture raw MLLP traffic using Wireshark with the MLLP dissector, or use your integration engine's built-in message logging. Key things to inspect in a captured session:
- Framing — does each message start with 0x0B and end with 0x1C 0x0D?
- ACK timing — how long between message send and ACK receipt? > 5 seconds indicates receiver processing issues
- ACK codes — any AE or AR responses?
- Sequence gaps — are MSH-10 (Control IDs) sequential? Gaps indicate lost messages
- Retransmissions — is the sender retransmitting messages the receiver already ACKed?
The HL7 Viewer Workflow
Our free HL7 Message Viewer supports paste-and-parse validation of any HL7 v2.x message with:
- Full field-by-field decode against segment definitions
- Table value lookups (coded fields)
- Validation against mandatory/optional field rules
- Batch processing for testing message sequences
- PDF export for compliance documentation
Use it as part of your daily troubleshooting workflow — paste a captured message, verify it decodes correctly, and identify field-level errors before engaging the sending system vendor.
6. The 12 Most Common HL7 Failures
1. Sending Application / Facility Mismatch
Symptom: Interface engine receives messages but routes them incorrectly or drops them.
Root cause: MSH-3 or MSH-4 values on the sending system don't match the engine's listener configuration.
Fix: Audit MSH-3 and MSH-4 on every sending system against your interface engine route table. Treat these as case-sensitive.
2. Processing ID = T in Production
Symptom: Messages arrive at engine but are discarded or routed to a dev destination.
Root cause: MSH-11 set to T (Test) when system went live without updating the configuration.
Fix: Add a hard rule in your engine to alert on any MSH-11 = T arriving on a production listener. Check MSH-11 during go-live verification.
3. Encoding Character Collision
Symptom: Patient names truncated, garbled field values, downstream system shows wrong data.
Root cause: Patient or location data contains ^, ~, \, or & characters unescaped.
Fix: Implement input validation on free-text fields before HL7 encoding. Escape special characters using the HL7 escape sequences (\F\ for pipe, \S\ for caret, etc.).
4. Missing ORC Segment in ORM
Symptom: Orders rejected by RIS with "Missing required segment ORC."
Root cause: Some EMR systems send OBR without the mandatory preceding ORC segment.
Fix: Add a transformation in the engine to synthesise a minimal ORC from OBR fields when ORC is absent. Document as a known non-compliance from the sending system.
5. Duplicate Placer Order Numbers
Symptom: RIS shows duplicate orders for the same patient.
Root cause: EMR reuses placer order numbers after a system reset, database restore, or sequence rollover.
Fix: Add deduplication logic in the engine that checks ORC-2 against a rolling window of received orders. Alert on duplicates rather than blindly forwarding.
6. A40 Merge Not Handled
Symptom: Merged patients in EMR appear as two separate patients in PACS/RIS with split imaging history.
Root cause: The receiving system's A40 handler is not configured or is silently discarding the merge message.
Fix: Explicitly test A40 during integration testing. Verify the receiving system merges the patient records and re-associates historical data.
7. Result Status Not Updated on Correction
Symptom: Corrected reports not re-notified to ordering physicians. Old report remains visible in EMR.
Root cause: ORU^R01 sent with OBR-25 = C (Corrected) but receiving system not configured to handle correction events distinctly from new results.
Fix: Configure receiving system to treat C result status as an update/override, not a new result. Implement downstream notification on corrections.
8. Date/Time Timezone Ambiguity
Symptom: Exam dates appearing one day off in one system, appointment reminders sent at wrong times.
Root cause: HL7 timestamps default to local time with no timezone offset. Systems in different timezones (or across DST boundaries) interpret the same timestamp differently.
Fix: Use the full DTM format with timezone offset: YYYYMMDDHHMMSS+HHMM. Standardise on UTC across all interface layers.
9. HL7 Version Mismatch
Symptom: Receiving system rejects messages with "Unsupported Version" in the ACK.
Root cause: Sender sends HL7 2.5 features (new segment types, extended field usage) to a system that only understands 2.3.
Fix: Check version compatibility during vendor procurement. Use transformation rules in the engine to downgrade message version where necessary. Document all version-specific transformations.
10. MLLP Connection Never Closes
Symptom: Interface appears active but messages stop flowing. Engine shows connection established but no messages processed.
Root cause: TCP connection stuck in CLOSE_WAIT state. One side has closed but the other hasn't released the socket.
Fix: Implement TCP keepalive on MLLP connections (typically 60-second intervals). Configure the interface engine to detect stale connections and re-establish after a timeout.
11. Queue Backup During Downtime
Symptom: After a receiver comes back online after downtime, messages arrive in the wrong order or are lost.
Root cause: Queuing strategy not configured — engine either drops messages when receiver is down or redelivers them out of sequence.
Fix: Configure durable message queuing on the engine with guaranteed delivery. Implement a configurable retry policy. For ADT, replay queued messages in strict chronological order to avoid incorrect patient state.
12. Missing ACK Causes Sender to Stall
Symptom: Sending system stops transmitting. Operator reports "HL7 interface appears hung."
Root cause: MLLP is synchronous — the sender waits indefinitely for an ACK. If the engine or receiver crashes after receiving the message but before sending the ACK, the sender stalls waiting.
Fix: Configure an ACK timeout (typically 30–60 seconds) on the sending system. Set the engine to always send an ACK even on transformation errors (using AE code), ensuring the sender is never left waiting.
7. HL7 and DICOM: The Integration Bridge
The most common cross-standard integration in healthcare imaging is the HL7 → DICOM bridge. A radiology order flows as:
EMR ──(ORM^O01)──► RIS ──(MWL C-FIND)──► Modality
The RIS receives the HL7 ORM, extracts patient demographics and order details, and populates them into the DICOM Modality Worklist (MWL) server. The modality queries the MWL via DICOM C-FIND and retrieves the worklist item to populate the image header.
Critical field mapping (HL7 → DICOM):
| HL7 Field | DICOM Tag | Notes |
|---|---|---|
| PID-3 | (0010,0020) Patient ID | Issuer of Patient ID from PID-3.4 → (0010,0021) |
| PID-5 | (0010,0010) Patient Name | PN encoding: Family^Given^Middle |
| PID-7 | (0010,0030) Patient Birth Date | Format conversion: YYYYMMDD |
| PID-8 | (0010,0040) Patient Sex | M/F/O direct mapping |
| OBR-4 | (0008,1030) Study Description | Procedure description |
| ORC-2 | (0008,0050) Accession Number | Placer order number |
| OBR-16 | (0008,0090) Referring Physician | PN encoding from XCN format |
Any mismatch between this mapping and the live field contents causes images to arrive at PACS with wrong demographics — one of the most operationally disruptive integration failures in radiology.
8. Security Considerations
Transport Security
HL7 v2 over plain MLLP provides zero security — no encryption, no authentication. In any environment where HL7 messages carry PHI:
- Wrap MLLP connections in TLS (MLLP over TLS, sometimes called MLLPS)
- Use mutual TLS (mTLS) where both parties present certificates
- Restrict MLLP listener ports to specific source IP ranges via firewall rules
- Log all connection events (established, closed, timed out)
PHI in Transit
Every HL7 message carries PHI. Treat HL7 traffic as you would any other PHI data stream:
- Encrypt in transit (TLS minimum)
- Encrypt message queues at rest
- Apply HIPAA minimum necessary — strip unnecessary PHI fields before routing to systems that don't need them
- Implement access controls on integration engine administration consoles
Audit Logging
Maintain a complete audit trail of all HL7 messages: message type, sender, receiver, timestamp, ACK code, and processing status. This is required for HIPAA compliance and is invaluable for incident investigation.
9. HL7 v2 to FHIR Migration
While most production systems still run on HL7 v2, the shift toward FHIR R4 is accelerating — particularly for patient-facing applications, payer integrations, and new EMR deployments. Key practical points for the transition:
- FHIR does not replace v2 overnight. Hybrid environments (v2 for backend, FHIR for APIs) will exist for a decade or more. Learn both.
- The HL7 v2-to-FHIR mapping project provides official mapping guidance for converting v2 segments to FHIR resources. Use it as a starting point, not a complete specification.
- Integration engines increasingly support FHIR. Most major engines (Mirth Connect, Rhapsody, Azure Health Data Services) can translate between v2 and FHIR. Invest in this capability rather than building manual converters.
- SMART on FHIR is becoming the standard for application-level access. Understand OAuth 2.0 and PKCE flows if you are building clinical-facing applications.
Conclusion
HL7 v2 integration is unglamorous work. It runs behind every clinical workflow, and when it fails, the consequences range from operational disruption to patient safety risk. The engineers who understand it deeply — who can read a raw MLLP capture, trace a message through a transformation chain, and diagnose a patient merge failure — are irreplaceable in any healthcare IT environment.
The principles in this guide apply whether you are using Mirth Connect in a community hospital or a commercial engine in an enterprise health system. Master the message anatomy, understand the ACK protocol, build a proper test library, and document every transformation you write.
Related resources: DICOM Protocol Reference · PACS Implementation Guide · HL7 Message Viewer (free tool)
Referenced Product
HL7 Message Viewer
The engineering-grade implementation toolkit that accompanies this guide. Built from the same real-world deployment experience covered above.