Almost always, when you send an HL7 v2 message, you need or get an acknowledgement message back from the destination system. This post describes how these acknowledgements work.
There’s 2 main uses for acknowledgement messages:
- You need some content returned from the destination (i.e. accepting an order, getting an answer to a query)
- You simply need to know that the message was received and processed without error
There’s a few corner cases where you don’t even need acknowledgement – the sender simply doesn’t care whether the receiver gets it or not. In the real world, the only case that I’ve seen for this is a vital signs monitor that sends an observation message with the current vital sign measurements once every minute. It’s assumed that some other system will raise an alert if the messages aren’t getting through. In every other case I’ve seen, there’s some kind of acknowledgement.
Each HL7 v2 message Event has a defined message type, and also a defined response message type. The most common response message is the simple ACK message:
That’s the v2.6 version, with the SFT and UAC segments that I’ve never seen in production. Otherwise, it’s got a message header (MSH), the MSA segment, which summarises the acknowledgement status of the message, and it may have one or more ERR segments that list specific issues in the message. I have seen ERR segments in production, but not often.
There are response messages with a much more complicated structure, such as the response to an R02 event, which is a full blown order message with an MSA added – a query for an order. In this post, I’m solely going to deal with the issues around message level acknowledgements.
The MSA Segment has the following fields:
|1||Acknowledgment Code||ID: Coded Value for HL7 Defined Tables||Acknowledgment code (0008)|
|2||Message Control ID||ST: String Data|
|3||Text Message||ST: String Data|
|4||Expected Sequence Number||NM: Numeric|
|5||Delayed Acknowledgment Type||-: withdrawn|
|6||Error Condition||CNE : Coded with No Exceptions||Message error condition codes (0357)|
The first field contains the acknowledgement code, where the possible values are:
|AA||1||Original mode: Application Accept – Enhanced mode: Application acknowledgment: Accept|
|AE||2||Original mode: Application Error – Enhanced mode: Application acknowledgment: Error|
|AR||3||Original mode: Application Reject – Enhanced mode: Application acknowledgment: Reject|
|CA||4||Enhanced mode: Accept acknowledgment: Commit Accept|
|CE||5||Enhanced mode: Accept acknowledgment: Commit Error|
|CR||6||Enhanced mode: Accept acknowledgment: Commit Reject|
I’ll discuss the enhanced mode acknowledgement below. So you can receive 3 different kinds of codes:
- Accept – the message was accepted without error
- Error – the message had an error and was rejected
- Reject – the message was rejected because of an error
I’ve always wished that there was some consistent difference between “error” and “rejection”, but I’m not at all sure what it is. In fact there’s no particular definitions of these two concepts. The best I can find is that a reject (CR) is returned if the one of the values of MSH-9-message type, MSH-12-version ID or MSH-11-processing ID is not acceptable to the receiving application, and an error is returned if the message cannot be accepted for any other reason (e.g., sequence number error). However this is rather confused by the table of values for MSA-6:
|0||1||Success: Message accepted|
|100||2||Error: Segment sequence error|
|101||3||Error: Required field missing|
|102||4||Error: Data type error|
|103||5||Error: Table value not found|
|200||6||Rejection: Unsupported message type|
|201||7||Rejection: Unsupported event code|
|202||8||Rejection: Unsupported processing id|
|203||9||Rejection: Unsupported version id|
|204||10||Rejection: Unknown key identifier|
|205||11||Rejection: Duplicate key identifier|
|206||12||Rejection: Application record locked|
|207||13||Rejection: Application internal error|
I don’t know how to make systematic sense of this. If the database is down, so you can’t accept the message, should you return an error, or a reject? Beats me. And in practice, implementers seem to pretty much toss a coin. I think I’ve seen more AEs than ARs, on about a 4:1 ratio.
I find this particularly frustrating because the question I always need to ask when I get an AE or an AR back is simple: should I send this message again?
In practice, the single most common cause of a message error/rejection being returned is some transient system error. Things like:
- database is down / backing up
- message handling process has become corrupt
- deadlock contention
- network unavailability
In all these cases, the correct thing to do is wait a while, and resend the same message again, and to keep doing so until it succeeds. If, on the other hand, the destination is working as expected, and there’s some content problem with the message, then what you want to do is to discard the message, make an entry some error log, and move on. So how can you tell the difference? Well, there’s no general method, and quite often, you can’t. I think this is one of the worst aspects of HL7 v2 messaging.
As a follow up to this, there’s a related question: do I need to send the messages in order or not? This is also a rather difficult question. There’s no technical requirement anywhere in the HL7 standard I know of that says that messages have to be delivered in order, but the messages reflect a series of transactions, and these generally do need to be delivered in order. Consider the following scenarios:
- An ADT feed pushing patient and episode updates out. What happens if you drop a patient merge message, and then send it later, after other transactions on the patient record? (and in practice, merge messages are particularly prone to cause issues)
- A results feed from a lab to an EHR. Lab results are amended periodically. What happens if you hold a message, and then release it – will it overwrite an subsequent version of the same lab report that was sent later?
In practice, this depends on the fine details of the identity and timestamp processing of the receiving system. HL7 has made no rules about how this works, so you can take nothing for granted. In practice, most v2 processing feeds are unreliable and fragile in this regard, whether from big or small vendors (that’s my experience) so it’s best to be very conservative unless you specifically know otherwise : If you get an error, hold everything until some knowledgeable human intervenes. Assuming, of course, that there is one available… where as there very often isn’t.
I’ve run out of time for now. I’ll make a follow up post that deals with enhanced mode acknowledgements, with the impact of intermediaries, and with a commentary about the sequence number protocol.