sondahub / HL7 v2 test server
HL7 v2 test server
A receiving HL7 v2 application that checks what it is sent the way an interface engine does — MSH, segment order, required fields, tables, dates — and answers with an ACK that says AA, AE or AR, with an ERR segment for every problem. Original and enhanced acknowledgment, versions 2.2 to 2.8.2, and queries that find the synthetic clinic’s patients. Over HTTP, or MLLP through a bridge.
Free and open: no key, no account. The patients are synthetic, the same as the FHIR server’s.
Connect
- HTTP
- POST https://api.sondahub.com/hl7
- Content-Type
- x-application/hl7-v2+er7; charset=utf-8, or text/plain
- MLLP
- through the bridge: node bridge.mjs hl7, then localhost:2675
- In Sonda
- an HL7 interface sending to localhost:2675
- Versions
- 2.2, 2.3, 2.3.1, 2.4, 2.5, 2.5.1, 2.6, 2.7, 2.7.1, 2.8, 2.8.1, 2.8.2
- Samples
- https://api.sondahub.com/hl7/samples
A receiving application, SONDAHUB, that checks each message the way an interface engine does and answers it: AA when it is right, AE with an ERR segment for every problem — where it is, what HL7 calls it, and what is wrong in words — and AR when it cannot take the message at all. Segments end in a carriage return; over MLLP each message is framed VT … FS CR.
curl -X POST https://api.sondahub.com/hl7 -H 'Content-Type: x-application/hl7-v2+er7; charset=utf-8' \ --data-binary $'MSH|^~\\&|MYAPP|MYCLINIC|SONDAHUB|SONDAHUB|20261007120000||ADT^A04^ADT_A01|MSG0001|T|2.5.1\rEVN||20261007120000\rPID|1||MRN900001^^^SONDAHUB^MR||Rivera^Ana^Lucia||19870412|F\rPV1|1|O\r'
MSH|^~\&|SONDAHUB|SONDAHUB|MYAPP|MYCLINIC|20261007120001+0000||ACK^A04^ACK|SHMUZ3K9X0001|T|2.5.1 MSA|AA|MSG0001
MSA|AE|MSG0008|PID-3 (patient identifier list) has no identifier. ERR||PID^1^3^1|101^Required field missing^HL70357|E||||PID-3 (patient identifier list) has no identifier. ERR||PID^1^7|102^Data type error^HL70357|E||||PID-7 (date of birth) is 19801332, not an HL7 date (YYYYMMDD). ERR||PID^1^8|103^Table value not found^HL70357|E||||PID-8 (administrative sex) is Z, which is not in table 0001: F, M, O, U, A, N, X.
GET https://api.sondahub.com/hl7/samples for ready messages — ADT^A04, ADT^A08, ORU^R01, ORM^O01, VXU^V04, QRY^A19, QBP^Q22, and an ADT^A01 with errors — made from a clinic patient (?mrn= to pick one, ?version=2.3 for another version).
In LockFlare Sonda
Sonda’s HL7 interface speaks MLLP, so it goes through the bridge:
curl -O https://sondahub.com/industrial/bridge.mjs node bridge.mjs hl7
Then, in LockFlare Sonda, make a new HL7 interface and type localhost:2675 where it asks where to send — no scheme, no tls:// — and press Connect. Paste this under Send and send it:
MSH|^~\&|SONDA|MYCLINIC|SONDAHUB|SONDAHUB|20261007120000||ADT^A04^ADT_A01|MSG0001|T|2.5.1 EVN||20261007120000 PID|1||MRN900001^^^SONDAHUB^MR||Rivera^Ana^Lucia||19870412|F PV1|1|O
The ACK shows under Messages: MSA|AA|MSG0001. Change PID-8 to Z, or the date of birth to 19871332, and the answer is AE with an ERR segment per problem. Through the bridge the receiver remembers: send the QRY^A19 from the samples with QRD-8 set to MRN900001 and the ADR^A19 brings back the patient you just registered. The Listen tab is not needed: the hub only answers, it never sends messages of its own. Without the bridge, an HTTP request does it too — POST the message to https://api.sondahub.com/hl7, segments ending in a carriage return.
What it takes
| Message | What the receiver does |
|---|---|
| ADT ADT^A01, ADT^A02, ADT^A03, ADT^A04, ADT^A05, ADT^A08, ADT^A11, ADT^A13, ADT^A28, ADT^A31, ADT^A40 | Admit, transfer, discharge, register, pre-admit, update, cancel admit, cancel discharge, add and update person, merge. A01, A04, A05 and A28 register the patient; A08 and A31 update one; A40 merges MRG-1 into PID-3. |
| ORU ORU^R01 | Observation results: OBR with its OBX segments, value types and result statuses checked. |
| ORM ORM^O01 | Orders: ORC with its order control code, and the OBR it orders. |
| OML OML^O21 | Laboratory orders: ORC and OBR. |
| SIU SIU^S12, SIU^S13, SIU^S14, SIU^S15 | Appointments booked, rescheduled, modified, cancelled: SCH. |
| MDM MDM^T02 | A document with its content: TXA and OBX. |
| VXU VXU^V04 | Vaccinations given: RXA with when, what and how much. |
| DFT DFT^P03 | Charges: FT1 with date, type and code. |
| QRY QRY^A19 | Patient query by MRN (QRD-8), answered with ADR^A19. |
| QBP QBP^Q22 | Find candidates by @PID.3.1, @PID.5.1, @PID.5.2, @PID.7 or @PID.8, answered with RSP^K22. |
What it checks
MSH: the separators it declares, MSH-7 a timestamp, MSH-9 a message type and event it takes, MSH-10 a control id, MSH-11 P, T or D, MSH-12 a version it takes — any of these wrong is an AR. Then the message: the segments its type needs, in order (EVN before PID before PV1; OBR before its OBX), and the fields that must be there and must be right — PID-3 an identifier, PID-5 a family name, PID-7 a real date, PID-8 in table 0001, PV1-2 in table 0004, OBX-2 in table 0125, OBX-5 a number when OBX-2 says NM, OBX-11 in table 0085, ORC-1 in table 0119, RXA-6 an amount — any of these wrong is an AE. A warning (an ORU with no OBX, an update for a patient nobody registered) is an AA with an ERR of severity W.
ERR is written for the version the message came in: from 2.5 on, ERR||PID^1^8|103^Table value not found^HL70357|E||||what is wrong; before, ERR|PID^1^8^103&Table value not found&HL70357.
| Error code (table 0357) | Meaning |
|---|---|
| 100 | Segment sequence error |
| 101 | Required field missing |
| 102 | Data type error |
| 103 | Table value not found |
| 200 | Unsupported message type |
| 201 | Unsupported event code |
| 202 | Unsupported processing id |
| 203 | Unsupported version id |
| 204 | Unknown key identifier |
| 205 | Duplicate key identifier |
| 207 | Application internal error |
Acknowledgment modes
Original mode — MSH-15 and MSH-16 empty — gets one ACK: AA, AE or AR. Enhanced mode gets what MSH-15 and MSH-16 ask for: a commit ACK (CA; CR when the message is refused) for AL, SU or an empty MSH-15, and then the application ACK (AA or AE) as a message of its own for AL, SU on success, ER on error. Over HTTP both come back in one answer, one after the other.
Queries
QRY^A19 asks for a patient by MRN in QRD-8 and gets ADR^A19: MSA, QAK (from 2.4), the QRD echoed, and PID and PV1 — or QAK NF when there is no such patient. QBP^Q22 finds candidates by the parameters in QPD-3 — @PID.3.1 (MRN), @PID.5.1 (family name), @PID.5.2 (given name), @PID.7 (date of birth, or its start), @PID.8 (sex), all of them matching — up to the count in RCP-2, and gets RSP^K22: QAK with the hits, the QPD echoed, and a PID and QRI for each.
QPD|Q22^Find Candidates^HL7|Q0002|@PID.5.1^[email protected]^20250314 RCP|I|10^RD
Questions
Over HTTP or over MLLP?
Either. POST a message to https://api.sondahub.com/hl7 and the ACK comes back in the answer — no bridge, from any HTTP client. Over TCP with MLLP framing, run node bridge.mjs hl7 and send to localhost:2675: through the bridge the receiver remembers what it is sent, so a patient registered with ADT^A04 is found by QRY^A19, and a message sent twice is answered twice but processed once.
Why port 2675?
It is HL7’s usual 2575 plus 100, so a listener you already run there keeps its port. The bridge takes the next free port for a second receiver, or the one you give it: node bridge.mjs hl7 2575.
Whose patients are they?
The synthetic clinic’s — the same patients the FHIR server serves, with the same MRNs — and the ones registered on the connection. None of them is a real person.
Does it send messages of its own?
Only answers: ACKs (and, in enhanced mode, a commit ACK before the application ACK), ADR^A19 and RSP^K22. An ACK sent to it is taken without an answer.