Healthcare/HL7 v2 in Python
Guide

How to receive HL7 v2 messages in Python

Receiving HL7 v2 takes an MLLP listener, a parser and an acknowledgment responder. This guide compares the Python libraries that provide them and sets up a production listener.

3libraries compared
MLLPtransport protocol
v2.9segments supported
adt_listener.py
# -*- coding: utf-8 -*-

# Zato
from zato.server.service import Service

class ADTListener(Service):
    name = 'demo.hl7.adt-listener'

    def handle(self) -> 'None':

        # The MLLP channel parsed the message,
        # the acknowledgment is sent for you
        msg = self.request.input

        patient_name = msg.pid.patient_name.family_name
        control_id = msg.msh.message_control_id

        self.logger.info('Received %s: %s',
            control_id, patient_name)

How do you receive HL7 v2 messages in Python?

Over MLLP - the Minimal Lower Layer Protocol, a thin TCP framing that hospital systems have used for decades. Each message arrives wrapped in a start byte and an end sequence on a TCP port, and the receiver must reply with an HL7 acknowledgment, an ACK message.

A working receiver therefore needs three parts - an MLLP listener that accepts TCP connections and unwraps frames, a parser that turns the pipe-delimited ER7 payload into something a program can read, and an ACK responder that tells the sender the message was accepted or rejected.

Which Python HL7 libraries exist?

Three main options cover different amounts of the job. python-hl7 and hl7apy are parsing libraries with basic MLLP pieces, Zato is an integration platform where MLLP channels are production infrastructure with routing, security and multi-server scaling around them.

LibraryLicenseParsingMLLPProduction features
python-hl7BSDGeneric, position-basedExperimental asyncio client and serverNone - a library, not a server
hl7apyMITValidation against message profilesA built-in MLLPServer classNone - single-threaded reference server
ZatoAGPLv3Typed classes for every v2.9 segmentProduction MLLP channelsACKs, routing, deduplication, TLS, REST bridge, multi-server scaling

For a script that parses a file of messages, a parsing library is enough. For a listener that hospital systems connect to around the clock, the production concerns - acknowledgments, retransmissions, routing, monitoring, high availability - are most of the work, and that is what a platform provides.

How do I set up an MLLP listener in Zato?

Create an MLLP channel in the Dashboard - name, TCP port and the service that will handle each message. The channel unwraps the MLLP framing, parses the ER7 payload into a typed message object, invokes the service and sends the acknowledgment back - an AA when the service completes, an AE with error details when it raises an exception.

lab_listener.py
# -*- coding: utf-8 -*-

# Zato
from zato.server.service import Service

class LabResultListener(Service):
    name = 'demo.hl7.lab-result-listener'

    def handle(self) -> 'None':

        # The channel already parsed the raw ER7 bytes ..
        msg = self.request.input

        # .. read what the integration needs ..
        control_id = msg.msh.message_control_id
        message_type = msg.get('MSH.9.1')

        # .. convert the whole message to JSON for downstream systems ..
        as_json = msg.to_json()

        self.logger.info('Received %s (%s), %d bytes of JSON',
            control_id, message_type, len(as_json))

        # .. returning nothing means Zato replies with an AA acknowledgment.

Send a test message to localhost:11553 with any MLLP client and the channel returns the acknowledgment as the response.

How do I parse an ADT^A01 and read specific fields?

Messages parse into typed objects - every segment of the v2.9 specification is a Python class and every field has a semantic name, as documented in field access and path expressions.

parse_adt.py
from zato.hl7v2 import parse_hl7

raw = (
    'MSH|^~\\&|SENDER|FACILITY|RECEIVER|FAC|20260315||ADT^A01^ADT_A01|CTL001|P|2.9\r'
    'EVN|A01|20260315\r'
    'PID|1||12345^^^HOSP^MR||SMITH^JOHN^A||19800115|M\r'
    'PV1|1|I|WARD^101^BED1\r'
)

message = parse_hl7(raw, validate=False)

# Semantic names ..
family_name = message.pid.patient_name.family_name    # 'SMITH'
patient_class = message.pv1.patient_class             # 'I'

# .. or positional path expressions ..
given_name = message.get('PID.5.2')                   # 'JOHN'
message_type = message.get('MSH.9.1')                 # 'ADT'

# .. and the whole message as JSON.
as_json = message.to_json(indent=2)

What about high volume and batch files?

HL7 batch envelopes - FHS and BHS headers wrapping many messages - arrive over the same channels, with the service receiving the full batch to unpack, as described in batch processing. File-based exchange works with the built-in scheduler - a job picks up files on an interval and feeds each message through the same services that handle MLLP traffic, so the processing logic exists once.

Frequently asked questions

Yes - senders wait for the acknowledgment and retransmit the message if none arrives. With Zato the ACK is automatic - AA on success, AE on error, AR when no channel matches - and deduplication catches the retransmissions that still happen.

There is no reserved port - 2575 is a common convention, but every deployment picks its own. The port is part of the MLLP channel definition.

Plain MLLP is unencrypted, so production traffic between organizations runs over TLS. Zato outgoing MLLP connections support TLS and mutual TLS with certificate configuration, and inbound traffic is typically terminated by the network layer in front of the listener.

Yes - MLLP channels have a REST bridge, so the same channel accepts messages over HTTP too, with the same parsing and the same service handling both transports.

Ready to receive HL7 v2 in Python?

Get started with Zato and set up your first MLLP listener in minutes.

Open source In Python