← All articles

Healthcare Integration For Beginners: HL7v2

At 1Putt Health, we work with engineering teams at digital health startups who are connecting to hospitals and health systems for the first time. The question we hear most often is not about FHIR — it is about HL7v2. Why does this ancient pipe-delimited format still dominate, how do you actually read one of these messages, and what do you need to know before you start building?

This is the guide we wish had existed when we started.

Why HL7v2 Still Runs Healthcare in 2026

HL7 version 2 was designed in 1987. It predates the web. It uses pipe characters as delimiters and carriage returns as segment terminators. By any modern software standard, it looks like it belongs in a museum.

It is also running in approximately 95% of US healthcare organizations and more than 35 countries worldwide. That number has not changed meaningfully in years and is not going to change fast. Here is why.

Healthcare IT infrastructure has decades of accumulated investment. A community hospital running a lab information system built in 2003, an EHR deployed in 2011, and a radiology PACS from 2015 all speak HL7v2 — because they were all built in an era when HL7v2 was the only viable interchange standard. Replacing that infrastructure costs millions of dollars and years of operational risk. Organizations do it incrementally if at all.

FHIR is the present and future of healthcare interoperability. It is also the standard that gets bolted on top of the HL7v2 infrastructure that is not going anywhere. For the foreseeable future, building for healthcare means building for both.

If you are connecting to a hospital, a lab, a pharmacy, or an imaging center that has not explicitly told you they have a FHIR R4 endpoint ready for production use, assume you are working with HL7v2.

The Structure of an HL7v2 Message

An HL7v2 message is a sequence of segments, each on its own line (terminated by \r). Each segment is a named group of fields. Fields within a segment are separated by pipes (|). Components within a field are separated by carets (^). Subcomponents are separated by ampersands (&). Repetitions within a field are separated by tildes (~).

A simple ADT^A01 message (patient admission) looks like this:

MSH|^~\&|SENDING_APP|SENDING_FACILITY|RECEIVING_APP|RECEIVING_FACILITY|20260314120000||ADT^A01^ADT_A01|MSG00001|P|2.5.1|||AL|NE|
EVN|A01|20260314120000|||
PID|1||MRN12345^^^HOSPITAL^MR||Doe^John^M||19800515|M|||123 Main St^^Austin^TX^78701^USA|||||||SSN123456789|
PV1|1|I|3 WEST^301^A^HOSPITAL^^^^HOSPITAL||||||||||||||ADM20260314|||||||||||||||||||||||||20260314120000|

Breaking that down segment by segment:

MSH — Message Header: Every HL7v2 message starts with MSH. It defines the sending application, sending facility, receiving application, receiving facility, timestamp, message type, message control ID, and version. The ^~\& immediately after MSH| is the encoding characters definition — it tells parsers which characters serve as component, repetition, escape, and subcomponent separators for the rest of the message.

EVN — Event Type: The trigger event that caused this message to be generated. For ADT^A01, this is the admission event, with timestamp.

PID — Patient Identification: The core patient demographics segment. PID-3 is patient identifier (the MRN), PID-5 is patient name in Last^First^Middle format, PID-7 is date of birth, PID-8 is administrative sex, PID-11 is address. PID is present in almost every HL7v2 message type.

PV1 — Patient Visit: Visit-level information. Patient class (inpatient, outpatient, emergency), assigned location, attending provider, admit date/time.

The Three Message Types You Will Encounter Most

ADT — Admission, Discharge, Transfer

ADT messages are the most common HL7v2 message type in existence. They communicate patient movement events: a new admission (A01), a discharge (A03), a transfer between units (A02), a registration update (A08), a patient merge (A40).

If you are building anything that needs to track patient presence in a facility, maintain a current patient list, or receive real-time notifications of patient status changes, you are consuming ADT messages.

The critical fields to validate:

  • PID-3: Patient identifier. This is your MRN. Validate it is present, properly formatted, and matches your patient identity system before you process anything else in the message.
  • PV1-2: Patient class. Determines whether this is inpatient, outpatient, emergency, or other.
  • PV1-3: Assigned patient location. Facility, building, floor, room, bed — encoded as a hierarchical string.

ORM — Order Message

ORM messages communicate clinical orders: lab tests, imaging studies, medications, procedures. An ORM^O01 is generated when a provider places an order in the EHR.

Key segments beyond MSH and PID:

ORC — Common Order: The order control segment. ORC-1 is the order control code — NW for a new order, CA for a cancellation, DC for discontinue. ORC-3 is the filler order number (assigned by the receiving system), ORC-2 is the placer order number (assigned by the ordering system).

OBR — Observation Request: The specific test or procedure being ordered. OBR-4 is the universal service identifier — typically a LOINC code identifying what was ordered. OBR-6 is requested date/time.

A note on version evolution: as of HL7v2.5, the spec recommends using message-type-specific order messages (OML for lab orders, OMG for general orders, OMP for pharmacy) rather than the generic ORM. In practice, ORM^O01 is still what you will see from most systems built before 2015. Know both.

ORU — Observation Result (Unsolicited)

ORU messages deliver results back to the ordering system. A lab analyzer completes a chemistry panel, a PACS system finalizes a radiology read, a remote monitoring device generates a vital signs measurement — all of these produce ORU^R01 messages.

Key segments:

OBR — Observation Request: In an ORU, OBR describes the original order that produced these results. OBR-7 is observation date/time (when the specimen was collected or the observation was made — critical for result validity).

OBX — Observation/Result: One OBX segment per individual result. A single ORU message for a comprehensive metabolic panel may contain 14 OBX segments — one for sodium, one for potassium, and so on.

  • OBX-3: Observation identifier. Should be a LOINC code. Map all incoming OBX-3 codes to LOINC in your system; do not trust that every sender uses consistent identifiers without normalization.
  • OBX-5: Observation value. The actual result. Data type varies — NM for numeric, ST for string, CWE for coded values.
  • OBX-6: Units of measure.
  • OBX-7: Reference range.
  • OBX-8: Abnormal flags. H for high, L for low, HH for critical high, LL for critical low.

MLLP: How HL7v2 Actually Moves Across a Network

HL7v2 messages are typically transmitted using the Minimal Lower Layer Protocol (MLLP), also called the HL7 Lower Layer Protocol. MLLP is a thin framing protocol over TCP/IP that wraps each message with start and end block characters so the receiving system knows exactly where each message begins and ends.

The wire format:

<SB> [HL7 message content] <EB><CR>

Where <SB> is the start block character (0x0B), <EB> is the end block character (0x1C), and <CR> is a carriage return (0x0D).

After receiving a message, the server sends back an acknowledgment — an ACK (Application Acknowledgment) message indicating successful receipt, or a NACK if something went wrong. The ACK/NACK mechanism ensures at-least-once delivery semantics over TCP.

The practical engineering problem: MLLP is a raw TCP socket protocol. There is no built-in TLS, no authentication, no message-level security. HL7v2 was designed for internal hospital networks in an era when the network perimeter was the security boundary. Connecting to a hospital interface engine over MLLP today means you are almost certainly tunneling through a VPN or IPSec connection, and the setup time for that network infrastructure — stakeholder meetings, security reviews, network configuration — often takes longer than the integration itself.

Budget at minimum four to six weeks for network access establishment. Do not let this be a surprise on your project timeline.

Parsing HL7v2 in Practice

Do not try to parse HL7v2 with string splits and regex. The encoding rules have enough edge cases — escaped delimiters, truncated optional fields, non-standard extensions — that home-grown parsers break on real-world data.

Use a library. In Node.js, node-hl7-client and simple-hl7 are both reasonable. In Python, hl7 (the python-hl7 package) is the standard. In Java, HAPI HL7 is the reference implementation. In .NET, nHapi covers most use cases.

A minimal Node.js example reading a PID segment:

import hl7 from 'node-hl7-client'

const raw = `MSH|^~\\&|LAB|HOSPITAL|EMR|HOSPITAL|20260314||ORU^R01|MSG001|P|2.5.1\rPID|1||MRN99999^^^HOSPITAL^MR||Smith^Jane^A||19750920|F\rOBR|1|ORD001|FIL001|2093-3^Cholesterol^LN|||20260314090000\rOBX|1|NM|2093-3^Cholesterol^LN||185|mg/dL|<200||||F`

const message = hl7.parse(raw)

const patientId = message.get('PID.3.1')   // MRN
const patientName = message.get('PID.5.1') // Last name
const cholesterol = message.get('OBX.5')   // Result value
const units = message.get('OBX.6')         // mg/dL
const abnormalFlag = message.get('OBX.8')  // Empty = normal

console.log(`Patient ${patientName} (${patientId}): Cholesterol ${cholesterol} ${units}`)

Z-Segments: The Custom Extensions That Break Everything

The HL7v2 spec allows any organization to define custom segments prefixed with the letter Z. Z-segments are where hospitals stuff whatever they need that the standard does not cover: custom identifiers, proprietary fields, local codes, facility-specific workflow flags.

You will encounter Z-segments. Every major health system has them. Some are well-documented. Most are not.

When you receive a message with Z-segments you have not seen before:

  1. Do not reject the message because of the unknown segment — most parsers can be configured to pass through unknown segments without error
  2. Log the full raw message for analysis
  3. Contact the sending organization's interface team and ask for the Z-segment spec
  4. Map Z-segment content to your internal data model only after you understand what it actually contains

Never assume Z-segment content based on field position alone. Two hospitals may use the same Z-segment name to mean completely different things.

What HL7v2 Cannot Do

HL7v2 is messaging, not querying. You receive messages when the sending system pushes them. You cannot query a hospital's HL7v2 interface for a specific patient's history — there is no request-response query model (beyond some specific workflow patterns that are rarely deployed).

If you need to pull historical data — a complete patient record, a date range of lab results, a full medication history — you need a different mechanism: FHIR APIs where available, HL7v2 query messages where configured (rare), or an HIE connection.

Design your integration architecture with this constraint clearly understood. HL7v2 is an event stream, not a database. Build accordingly.


Building Your First HL7v2 Integration?

HL7v2 integrations are deceptively complex — network setup, segment variations, Z-segment documentation gaps, and per-site configuration testing add up quickly. 1Putt Health helps digital health startups connect to hospitals and health systems through HL7v2 interface engines, FHIR APIs, and direct EHR integrations. We handle the interface complexity so your team can focus on your product.

Book a discovery call to scope your integration requirements.