What it does
Outside partners can't open MLLP connections into the hospital, but they can call a web service. One HTTPS port (8443) serves two routes, told apart by their path. Both deliver to MEDITECH (a stand-in that answers AR "patient not found" for unknown patients) over MLLP:
| Route | Source | Mode | What the partner gets back |
|---|---|---|---|
reference-lab-results | REST POST /hl7/results, one API key per lab, JSON ACK | store-and-forward | 200 with ack_code AA as soon as the result is stored. If MEDITECH rejects it (AR), it goes to dead letters. |
clinic-registrations | SOAP 1.1 POST /Hl7Service, basic auth | pass-through | MEDITECH's own ACK inside the SOAP response: AA, or AR with MEDITECH's reason. AE from the gateway if MEDITECH can't be reached. |
The sample is in 02-partners-to-meditech in the examples repository.
Diagrams

architecture.mmd
data-flow.mmd
sequence.mmdThe configuration
02-partners-to-meditech/gateway.toml
# Sample 02: partners → HTTPS (REST and SOAP) → HL7 Edge Gateway → MLLP → MEDITECH
#
# One HTTPS port (8443) serves two routes, told apart by their path:
# reference-lab-results POST /hl7/results REST, a raw HL7 message as the body.
# Each partner has its own API key; the key's name is logged as the caller.
# Store-and-forward: the partner gets 200 once the result is stored, and the
# gateway delivers it to MEDITECH in order, retrying while MEDITECH is down.
# clinic-registrations POST /Hl7Service SOAP 1.1, the HL7 message inside the envelope.
# Pass-through: the clinic gets MEDITECH's own ACK (AA, or AR with the reason)
# in the SOAP response. If MEDITECH cannot be reached, the gateway answers AE
# itself, and the clinic resends later.
#
# Secrets come from .env through ${NAME}.
node_id = "gateway-1"
[admin]
listen = "0.0.0.0:8080"
token = "${HL7EDGE_ADMIN_TOKEN}"
allow_payload_view = true
[store]
backend = "embedded"
path = "/var/lib/hl7-edge-gateway"
[[route]]
name = "reference-lab-results"
mode = "store_and_forward"
[route.source]
type = "http"
listen = "0.0.0.0:8443"
path = "/hl7/results"
ack_format = "json" # {"ack_code": "AA", "control_id": "…", "text": "…", "ack": "MSH|…"}
tls = { cert = "/etc/hl7-edge-gateway/certs/gateway.crt", key = "/etc/hl7-edge-gateway/certs/gateway.key" }
# allow_from = ["203.0.113.0/24"] # in production, also limit each partner to its address range
auth = { type = "api_key", header = "X-API-Key", keys = { northside-reference-lab = "${NORTHSIDE_LAB_API_KEY}", valley-pathology = "${VALLEY_PATHOLOGY_API_KEY}" } }
[route.destination]
type = "mllp"
address = "meditech:6661" # MEDITECH's inbound results interface
[route.store_and_forward]
on_reject = "dead_letter" # MEDITECH answered AR: park it, keep the rest flowing
retry = { initial = "2s", max = "30s", multiplier = 2.0 }
[[route]]
name = "clinic-registrations"
mode = "passthrough"
[route.source]
type = "http"
listen = "0.0.0.0:8443" # the same port; the TLS settings must match
path = "/Hl7Service"
format = "soap"
tls = { cert = "/etc/hl7-edge-gateway/certs/gateway.crt", key = "/etc/hl7-edge-gateway/certs/gateway.key" }
auth = { type = "basic", username = "lakeside-clinic", password = "${CLINIC_SOAP_PASSWORD}" }
[route.destination]
type = "mllp"
address = "meditech:6662" # MEDITECH's inbound ADT interface
02-partners-to-meditech/.env.example
# Copy to .env and fill in. .env is ignored by git.
LAMBOGENIUS_LICENSE=
HL7EDGE_ADMIN_TOKEN=change-me-to-a-long-random-string
# One API key per partner (send it as X-API-Key). Use long random values in production.
NORTHSIDE_LAB_API_KEY=northside-sample-key-4f8a2c
VALLEY_PATHOLOGY_API_KEY=valley-sample-key-9b31d7
# Basic auth password for the clinic's SOAP calls (user lakeside-clinic).
CLINIC_SOAP_PASSWORD=clinic-sample-password
GATEWAY_VERSION=0.3.0
A one-off certs container creates a test CA and a certificate for localhost in ./certs on the first start:
02-partners-to-meditech/compose.yaml
# Sample 02: partners → HTTPS (REST and SOAP) → HL7 Edge Gateway → MLLP → MEDITECH
# docker compose up -d creates a test certificate, starts the gateway and a stand-in MEDITECH
name: hl7edge-sample-02
services:
# One-off: a test CA and a server certificate for the gateway, in ./certs (kept if already there).
certs:
image: alpine:3.20
volumes: ["./certs:/certs"]
entrypoint: ["sh", "-c"]
command:
- |
[ -f /certs/gateway.crt ] && exit 0
apk add --no-cache -q openssl && cd /certs &&
openssl req -x509 -newkey rsa:2048 -nodes -days 365 -subj "/CN=Sample 02 test CA" -keyout ca.key -out ca.crt 2>/dev/null &&
openssl req -newkey rsa:2048 -nodes -subj /CN=localhost -keyout gateway.key -out gateway.csr 2>/dev/null &&
printf 'subjectAltName=DNS:localhost,DNS:gateway,IP:127.0.0.1\n' > san.ext &&
openssl x509 -req -in gateway.csr -CA ca.crt -CAkey ca.key -CAcreateserial -days 365 -extfile san.ext -out gateway.crt 2>/dev/null &&
chmod 644 gateway.key && rm gateway.csr san.ext ca.srl && echo "test certificates created in ./certs"
gateway:
image: lambogenius/hl7-edge-gateway:${GATEWAY_VERSION:-0.3.0}
env_file: .env
ports:
- "8443:8443" # HTTPS: REST /hl7/results and SOAP /Hl7Service
- "8080:8080" # admin console
volumes:
- ./gateway.toml:/etc/hl7-edge-gateway/hl7-edge-gateway.toml:ro
- ./certs:/etc/hl7-edge-gateway/certs:ro
- queue:/var/lib/hl7-edge-gateway
depends_on:
certs: { condition: service_completed_successfully }
meditech: { condition: service_started }
# Stand-in for MEDITECH's inbound MLLP interfaces (results on 6661, ADT on 6662).
meditech:
image: python:3.12-slim
command: ["python", "-u", "/app/receiver.py"]
volumes: ["./meditech:/app:ro"]
volumes:
queue:
Step by step
- 1Start it.
cd 02-partners-to-meditech cp .env.example .env # add your license docker compose up -dThe commands below are for Linux and macOS. On Windows, use
curl.exewith--ssl-no-revoke(Windows can't check revocation for a test CA), or run.\demo.ps1. - 2A reference lab posts a result with its API key.
curl --cacert certs/ca.crt \ -H "X-API-Key: northside-sample-key-4f8a2c" \ -H "Content-Type: x-application/hl7-v2+er7" \ --data-binary @messages/oru-r01-ref-1.hl7 \ https://localhost:8443/hl7/results {"ack":"MSH|^~\\&|MEDITECH|…\rMSA|AA|REF-3001\r","ack_code":"AA","control_id":"REF-3001","text":""} # A wrong or missing key never reaches the queue: HTTP 401 missing or unknown API key in x-api-keyWith
RUST_LOG=info,hl7_edge_gateway=debug, the log names the partner by its key:caller="northside-reference-lab". - 3The clinic registers a patient over SOAP.
The request is an ordinary SOAP 1.1 envelope, with the HL7 message as the text of an element:
02-partners-to-meditech/messages/soap-adt-a04.xml<?xml version="1.0" encoding="utf-8"?> <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body> <SubmitHL7 xmlns="urn:lakeside-clinic:hl7"> <message>MSH|^~\&|LAKESIDE_EHR|LAKESIDE_CLINIC|MEDITECH|GENERAL_HOSPITAL|20261019110000||ADT^A04^ADT_A01|CLN-4001|T|2.5.1 EVN|A04|20261019110000 PID|1||100131^^^GENERAL_HOSPITAL^MR||LEE^MIN||19840722|F PV1|1|O|CLINIC^^^LAKESIDE </message> </SubmitHL7> </soap:Body> </soap:Envelope>curl --cacert certs/ca.crt -u lakeside-clinic:clinic-sample-password \ -H 'Content-Type: text/xml; charset=utf-8' -H 'SOAPAction: "urn:lakeside-clinic:hl7/SubmitHL7"' \ --data-binary @messages/soap-adt-a04.xml https://localhost:8443/Hl7Service <soap:Envelope …><soap:Body><SubmitHL7Response xmlns="urn:lakeside-clinic:hl7"> <ack>MSH|^~\&|MEDITECH|… MSA|AA|CLN-4001|filed </ack> </SubmitHL7Response></soap:Body></soap:Envelope>The gateway found the HL7 text by itself (the first element that starts with
MSH) and answered with<SubmitHL7Response><ack>, in the request's namespace. An unknown patient gets MEDITECH's AR in the same place:MSA|AR|CLN-4099|patient not found: 555002.
Both HTTPS routes on one port. The SOAP route's last error is MEDITECH's AR for the unknown patient. - 4
- 5MEDITECH comes back.
docker compose start meditech. Within 30 seconds the two results are filed, in order. - 6
Adapting it
- Certificate: use one your partners trust. Every route on a port must have the same
tls. - Partners: one entry per partner under
keys, andallow_fromwith each partner's address ranges. For stronger authentication, use mutual TLS (tls.client_ca) or OAuth2 tokens. See Security. - A different WSDL:
soap = { message_element = "…", response_template = "…" }, orversion = "1.2". See HTTP, SOAP and MLLP out.

