NEWFree ROI Calculators — quantify what prior auth and siloed data are costing your organization.Prior Auth ROI Siloed Data ROI
HL7 v2Transport10 min read

HL7 Transport: Raw TCP/IP

Raw TCP/IP is an unframed transport for HL7 messages over plain sockets without the MLLP framing bytes (0x0B, 0x1C, 0x0D). Instead, the application layer must define its own message boundary mechanism—either a length prefix, a custom delimiter, or a higher-level protocol. This approach is less common than MLLP but is used in legacy systems, proprietary integrations, and scenarios where MLLP's framing is not available or undesirable.

What is Raw TCP/IP

Raw TCP/IP refers to the direct transmission of HL7 messages over a TCP socket stream without an intervening protocol like MLLP. The TCP layer itself is connectionless from an HL7 perspective; it simply provides a reliable, in-order byte stream. The burden falls on the HL7 application to determine where one message ends and the next begins.

Common approaches include:

  • Length prefix: Prepend a fixed- or variable-length field indicating the message byte count.
  • Custom delimiter: Define a non-standard delimiters (e.g., a newline, a custom record separator, or a timeout).
  • Timeout-based: Assume a silent period indicates message completion (fragile and error-prone).
  • HL7 over HTTP: Embed HL7 messages in HTTP request/response bodies (also referred to as HoH — HL7 over HTTP).

Raw TCP/IP is almost never used for vanilla HL7 v2 interchange; MLLP is the standard. However, understanding raw TCP/IP is useful for troubleshooting broken connections, implementing custom transports, or integrating with systems that do not support MLLP.

How it works

Raw TCP/IP operates as a simple TCP client-server pair. The sender opens a socket, transmits data (HL7 message or bytes), and either closes the connection or waits for a response. The receiver listens on a port, accepts the connection, reads from the socket, and responds (if required).

Connection Model

Unlike MLLP, raw TCP/IP does not define whether connections are persistent or transient:

  • Transient (per-message): The sender opens a connection for each message, transmits it, waits for a response, and closes the connection. This is simple but higher-latency and more resource-intensive.
  • Persistent (multiplexed): The sender maintains a long-lived connection and transmits multiple messages, relying on application-level message boundaries. This is more efficient but requires careful frame synchronization.

Message Boundary Problem

Without MLLP framing, the receiver cannot automatically detect where one message ends and the next begins. Here are the common failure scenarios:

Scenario 1: Message truncation (transient connection, no length field)

Sender              Receiver
  |                    |
  +-- "MSH|^~&|..." -->|
  |                    | (Does the message end? No way to know!)
  +-- (close) -------->|
  |                    | (Receiver realizes connection closed; end of message)

Scenario 2: Message coalescence (persistent connection, no length field)

Sender              Receiver
  |                    |
  +-- "MSH|..." ------->|
  +-- "MSH|..." ------->|
  |                    | (Receiver sees "MSH|...MSH|..." as one stream)
  |                    | (Parser cannot determine the boundary)

Length-Prefix Framing (Common Approach)

Many healthcare systems use a length-prefix scheme, similar to SSH's Binary Packet Protocol:

Message = [Length: uint32] [Payload: variable bytes]

Example (bytes):
  00 00 04 B2    <- Length = 1202 bytes (big-endian uint32)
  4D 53 48 7C ... <- HL7 message payload (1202 bytes)
  00 00 02 C8    <- Next message length = 712 bytes
  4D 53 48 7C ... <- Next HL7 message payload (712 bytes)

The receiver reads 4 bytes to determine the payload length, then reads exactly that many bytes, and repeats. This avoids ambiguity and allows multiple messages on a single persistent connection.

Custom Delimiter Approach (Legacy)

Some older systems use custom delimiters (e.g., |END|, a null byte , or a newline n):

Example (with "***END***" delimiter):
  MSH|^~&|SYSTEM1|FAC1|SYSTEM2|FAC2|...***END***
  MSH|^~&|SYSTEM1|FAC1|SYSTEM2|FAC2|...***END***

This is fragile because:

  • The delimiter might appear in a message field (e.g., in a note or free-text field).
  • Different HL7 versions and encodings handle delimiters inconsistently.
  • It is not a published standard.

Wire-level Sequence (with length prefix)

Sender                                Receiver
  |                                        |
  +--- TCP SYN (port 5000) ----------------->|
  |<------- TCP SYN-ACK -------------------+
  +--- TCP ACK ------------------------------->|
  |                                        |
  +--- [len=1202] [ADT payload] ------------->|
  |                                        |
  |<------- [len=10] [MSA|AA|...] --------- +
  |                                        |
  +--- [len=712] [ORU payload] ------------->|
  |                                        |
  |<------- [len=10] [MSA|AA|...] --------- +
  |                                        |
  +--- TCP FIN -------------------------------->|
  |<------- TCP FIN-ACK --------------------+

When to use it

Raw TCP/IP is rarely the right choice for new HL7 integrations. Use it only when:

  • Legacy system constraints: The receiving system does not support MLLP and has a custom TCP protocol.
  • Custom protocol requirement: Your organization has defined a proprietary length-prefix or delimiter scheme that all systems adhere to.
  • Internal, controlled network: The network is isolated and monitored; you can ensure consistent framing across all participants.
  • Testing or development: You are building a proof-of-concept or testing network connectivity without full protocol support.

Prefer MLLP for production healthcare integrations. It is battle-tested, standardized, and widely supported.

Do NOT use raw TCP/IP if:

  • The receiving system supports MLLP (use MLLP instead).
  • You need vendor support or professional integration services (vendors will not support non-standard framing).
  • Message delivery certainty is critical (the lack of a standard framing makes errors hard to detect).
  • You are integrating with commercial EHRs, labs, or pharmacies (all use MLLP or HTTP, not raw TCP).

Configuration parameters

Port

Any TCP port can be used; there is no standard default. Common choices are 5000, 6000, 7000, or any unused port above 1024. The sender and receiver must agree on the port in advance.

Connection Timeout

Define a socket timeout (e.g., 30–60 seconds) to avoid indefinite hangs:

  • Connect timeout: Maximum time to establish a new connection (typically 10–30 seconds).
  • Read timeout: Maximum time to wait for data on an open socket (typically 30–120 seconds).
  • Write timeout: Maximum time to send data (typically 30 seconds).

Frame Synchronization Method

Choose and document one of the following:

  • Length prefix (recommended): Prepend a fixed-size integer (e.g., 4-byte big-endian uint32) with the message byte count.
  • Custom delimiter (not recommended): Define a unique byte sequence that will never appear in HL7 messages. Document it explicitly.
  • Transient connection: Close the connection after each message (simple but inefficient).
  • Timeout: Assume silence for N seconds indicates end of message (fragile; not recommended).

Encoding

Use consistent character encoding (e.g., UTF-8 or ISO-8859-1) across sender and receiver. Unlike MLLP, raw TCP/IP has no framing-byte collision risk, so any encoding is technically viable.

Security

Raw TCP/IP provides the same security baseline as MLLP: only what the TCP layer offers. All security must be layered above or below:

TLS/SSL Encryption

Wrap the TCP connection in TLS 1.2 or later before transmitting any HL7 data:

Sender          Receiver
  |                 |
  +-- TCP connect -->|
  |                 |
  +-- TLS handshake -->|
  |<-- TLS handshake --+
  |                 |
  +-- [len] [HL7 encrypted] -->|

Use mutual TLS (mTLS) with certificate authentication to verify both parties.

Application-level Authentication

Since raw TCP/IP is non-standard, implement authentication at the application layer:

  • Shared secret in first message: Include an API key or token at the start of the stream.
  • Out-of-band credential exchange: Negotiate credentials before opening the data channel.
  • Network-level ACLs: Restrict access to known sender IP addresses via firewall.

Message Integrity

Add integrity verification to the payload if needed:

  • HMAC or digital signature: Include a checksum or signature in the HL7 message itself.
  • TCP checksum: Relies on TCP's built-in checksum, which is weak against intentional tampering.

Pros & cons

Advantages

  • Minimal overhead: No framing bytes; message payload goes directly on the wire.
  • Simplicity (for defined protocols): If your organization has a standard length-prefix scheme, implementation is straightforward.
  • Flexible frame boundaries: You can choose the framing method that fits your architecture.
  • No framing-byte collision: Unlike MLLP, you do not need to worry about encoding conflicts with 0x0B, 0x1C, 0x0D.

Disadvantages

  • No standard: Every implementation is different; no vendor support or off-the-shelf tools.
  • Message boundary ambiguity: Without careful frame definition, messages can be truncated, coalesced, or corrupted.
  • Hard to debug: Binary frames and unclear message boundaries make logs difficult to read.
  • Timeout-prone: Ambiguous frame boundaries often lead to retry storms or silent failures.
  • Not interoperable: Systems must implement the exact framing scheme; no fallback or auto-detection.
  • Vendor rejection: Healthcare vendors (Epic, Cerner, etc.) will not support custom TCP framing; they require MLLP.
  • Testing nightmare: Without a standard, testing and validation are complex and error-prone.

Examples

Wire-level capture with length-prefix framing

Here is a raw TCP/IP exchange using a 4-byte big-endian length prefix:

Sender TCP stream (hex bytes):

00 00 04 B2  <- Message 1 length = 1202 bytes
4D 53 48 7C 5E 7E 5C 26 7C ... [1202 bytes of HL7 data] ... 0A

00 00 02 C8  <- Message 2 length = 712 bytes
4D 53 48 7C 5E 7E 5C 26 7C ... [712 bytes of HL7 data] ... 0A

Receiver response stream (hex bytes):

00 00 00 0F  <- Response 1 length = 15 bytes
4D 53 41 7C 41 41 7C 4D 53 47 30 30 31 0A <- "MSA|AA|MSG001n"

00 00 00 0F  <- Response 2 length = 15 bytes
4D 53 41 7C 41 41 7C 4D 53 47 30 30 32 0A <- "MSA|AA|MSG002n"

Annotated frame

Incoming message with length prefix (shown in readable form):

[Length field (4 bytes, big-endian)] [HL7 Payload]

Example:
  00 00 05 20  <- 1312 bytes follow
  
  MSH|^~&|MYLAB|LAB|REGISTRY|STATE|20260701120000||ORU^R01^ORU_R01|MSG00001|P|2.5.1
  PID|||LAB123^^^LAB||Doe^John||19700101|M|||123 Main St^^Chicago^IL^60601
  OBR|1|LAB123|LAB123-1|1001-1^Glucose^LN|||20260701|
  OBX|1|NM|1001-1^Glucose^LN||95|mg/dL|70-100|N|||F
  
  (Total: 1312 bytes including segment delimiters and field separators)

Sample configuration (length-prefix scheme)

A pseudo-code configuration for a raw TCP/IP integration with length-prefix framing:

Transport:
  Type: RawTCP
  Protocol: HL7v2
  FramingMethod: LengthPrefix
  LengthPrefixSize: 4  # 4 bytes = uint32
  LengthPrefixByteOrder: BigEndian
  IncludeLengthInPayload: false  # length field is separate

Connection:
  Host: lab-system.hospital.local
  Port: 5000
  PersistentConnection: true
  ConnectTimeout: 30000  # milliseconds
  ReadTimeout: 60000
  WriteTimeout: 30000

Security:
  UseTLS: true
  TLSVersion: 1.2
  MutualAuth: true
  ClientCertificate: /etc/ssl/certs/client-cert.pem
  ClientKey: /etc/ssl/certs/client-key.pem
  TrustCertificates: /etc/ssl/certs/ca-bundle.pem

Encoding:
  CharacterSet: UTF-8
  SegmentDelimiter: r  # carriage return
  FieldDelimiter: |
  ComponentDelimiter: ^
  RepetitionDelimiter: ~
  EscapeCharacter: 

Failure-mode example

Scenario 1: Missing length prefix

Sender transmits HL7 without length prefix; receiver assumes connection-close signals end-of-message:

Sender logs:
  [12:00:45] TCP: Connected to receiver:5000
  [12:00:45] TCP: >> [1202 bytes] (no length header)
  [12:00:45] TCP: << Waiting for response...
  [12:01:15] TIMEOUT: No response after 30 seconds
  [12:01:15] TCP: Connection closed

Receiver logs:
  [12:00:45] TCP: Accepted connection from sender:54321
  [12:00:45] TCP: << Reading data... (waits for framing info)
  [12:01:00] TCP: Connection closed by peer (timeout)
  [12:01:00] ERROR: Incomplete message; no frame boundary detected

Scenario 2: Length-prefix mismatch

Sender declares a message length of 1000 bytes but only sends 500; receiver hangs waiting for data:

Sender logs:
  [12:00:45] TCP: >> Length=1000, Payload=500 bytes (BUG: insufficient data)
  [12:00:45] TCP: Connection closed

Receiver logs:
  [12:00:45] TCP: << Length=1000 bytes declared
  [12:00:45] TCP: Reading 1000 bytes...
  [12:00:46] TCP: Received 500 bytes
  [12:00:46] TCP: << Waiting for remaining 500 bytes...
  [12:01:15] TIMEOUT: Connection inactive for 30 seconds
  [12:01:15] ERROR: Incomplete message: got 500/1000 bytes
  [12:01:15] ALERT: Message discarded; sender error suspected

Scenario 3: Character encoding conflict with custom delimiter

Sender uses a custom delimiter |END| but HL7 data contains that sequence in a free-text field:

HL7 message:
  MSH|^~&|...|Note: Contact to schedule|END|of care...
                                        ^-- Oops! Delimiter appears in data
  
Receiver parsing:
  Message 1 ends at: "Note: Contact to schedule"
  Message 2 starts at: "of care..."
  
Result: Two malformed messages instead of one correct message

Vorro support for Raw TCP/IP

Vorro provides limited, custom support for raw TCP/IP integrations:

  • Custom transport adapters: Vorro engineers can build proprietary length-prefix or delimiter-based transport layers if needed.
  • TLS/mTLS encryption: All TCP connections support TLS encryption and mutual authentication.
  • Frame validation: Vorro logs frame boundaries and lengths to help troubleshoot message coalescence or truncation.
  • Fallback to MLLP: When possible, Vorro recommends migrating from raw TCP/IP to MLLP for standard compliance and vendor support.

Raw TCP/IP is not a standard Vorro transport; integrations using raw TCP/IP require custom configuration and ongoing support.

Sources

← Back to HL7 v2 Guide

Ready to Integrate This Into Your Workflow?

Talk to a Vorro expert about implementing HL7 v2 in your specific environment.

Browse HL7 v2 Guides