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.

3 libraries compared

MLLP transport protocol

v2.9 segments 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)
```

Direct answer

## 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 converts the pipe-delimited ER7 payload into data structures a program can read, and an ACK responder that tells the sender the message was accepted or rejected.

Libraries

## 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.

| Library | License | Parsing | MLLP | Production features |
| --- | --- | --- | --- | --- |
| python-hl7 | BSD | Generic, position-based | Experimental asyncio client and server | None - a parsing library |
| hl7apy | MIT | Validation against message profiles | A built-in MLLPServer class | None - single-threaded reference server |
| Zato | AGPLv3 | Typed classes for every v2.9 segment | Production MLLP channels | ACKs, 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.

Listener

## How do I set up an MLLP listener in Zato?

Create an [MLLP channel](https://zato.io/docs/dev/healthcare/hl7v2/mllp/index.html) 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.

Parsing

## 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](https://zato.io/docs/dev/healthcare/hl7/v2/field-access/index.html) and [path expressions](https://zato.io/docs/dev/healthcare/hl7/v2/field-access/index.html#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)
```

Volume

## 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](https://zato.io/docs/dev/healthcare/hl7/v2/batch/index.html). 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.

FAQ

## 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.

Related

## Keep reading

[Mapping HL7 v2 to FHIR mapping reference with Python](https://zato.io/integrations/healthcare/hl7-v2-to-fhir-mapping/) [Glossary HL7 vs FHIR vs MLLP - definitions](https://zato.io/integrations/healthcare/hl7-vs-fhir-mllp-glossary/) [Docs Receiving HL7 v2 over MLLP](https://zato.io/docs/dev/healthcare/hl7v2/mllp/index.html)

## Ready to receive HL7 v2 in Python?

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

[Get started ▶](https://zato.io/docs/admin/guide/install/index.html) [Read the docs](https://zato.io/docs/index.html)

Open source In Python
