Every HL7 v2 message generates a response. That response — the acknowledgement, or ACK — is how the sender knows whether to move on, retry, or alert an operator. Get ACK handling right and a busy interface runs silently for years. Get it wrong and you produce duplicates, lose messages, or create retry storms that pile up queues and knock out downstream systems at 3 a.m.
This article is the single reference we hand to new engineers on the integration team. It covers the MSA segment, the ERR segment, the three acknowledgement codes (AA/AE/AR), the commit-vs-application distinction, enhanced mode, and how Mirth Connect generates and consumes ACKs by default and when overridden.
If you are new to HL7 entirely, read HL7 v2 message structure explained first, then come back here. For the broader interface ecosystem, see our HL7 integration guide.
1. What an HL7 ACK Actually Is
An HL7 ACK is a full HL7 v2 message — not a single byte, not a status code, but a complete MSH-plus-MSA structure framed in MLLP exactly like the triggering message. The minimum legal ACK is two segments:
MSH|^~\&|RECEIVER|FACILITY|SENDER|FACILITY|20260421093001||ACK^A01|ACK00001|P|2.5
MSA|AA|MSG00001The message type in MSH-9 is typically ACK or ACK^{trigger} — for example ACK^A01 when acknowledging an ADT^A01. Some receivers use generic ACK; some echo the trigger. Both are legal; pick one convention and stick with it per interface.
Key invariants for any well-formed ACK:
- MSH-10 of the ACK is a new control ID generated by the receiver.
- MSA-2 of the ACK echoes MSH-10 of the original message — this is how the sender correlates.
- MSH-3/4 and MSH-5/6 are swapped relative to the original (sender becomes receiver and vice versa).
- Version (MSH-12) matches the original — do not "upgrade" the version in the ACK.
Missing any of the above is a frequent source of "the ACK came back but my sender still timed out" bugs. The sender was looking for an MSA-2 match against MSH-10 and didn't find one.
2. The MSA Segment
MSA is the Message Acknowledgement segment. It has six fields (v2.5+), though most production ACKs use only the first three.
MSA|AA|MSG00001|Message accepted|||0- MSA-1 Acknowledgment Code —
AA,AE,ARin original mode; addsCA,CE,CRin enhanced mode. - MSA-2 Message Control ID — echoes MSH-10 of the triggering message. Required.
- MSA-3 Text Message — free-text description of the outcome; optional but recommended for errors.
- MSA-4 Expected Sequence Number — rarely used outside sequence-numbering protocols.
- MSA-5 Delayed Acknowledgment Type — deprecated in v2.4+; do not use.
- MSA-6 Error Condition — deprecated in v2.5+; use the ERR segment instead.
Example with a human-readable failure message:
MSH|^~\&|LAB|HOSPITAL|EMR|HOSPITAL|20260421093100||ACK^R01|ACK00077|P|2.5
MSA|AE|MSG00077|PID-3 missing medical record number3. The ERR Segment
ERR replaced MSA-6 in HL7 v2.5 as the canonical way to carry structured error information. A well-formed error ACK uses MSA + ERR together.
MSH|^~\&|LAB|HOSPITAL|EMR|HOSPITAL|20260421093100||ACK^R01|ACK00077|P|2.5
MSA|AE|MSG00077|Validation failure
ERR||PID^1^3|101^Required field missing^HL70357|E|||Medical record number requiredKey ERR fields:
- ERR-2 Error Location — segment/segment-sequence/field/component. Here: PID, segment 1, field 3.
- ERR-3 HL7 Error Code — coded per HL7 table 0357 (or vendor-specific). Common values include
0(Message accepted),100(Segment sequence error),101(Required field missing),102(Data type error),103(Table value not found). - ERR-4 Severity —
E(Error),W(Warning),I(Information). - ERR-8 User Message — human-readable text. This is what an operator reads at 2 a.m.
You can have multiple ERR segments in a single ACK to report several problems at once. Most production receivers report only the first and stop processing; be defensive.
4. AA vs AE vs AR — Decision Logic
Choosing the right acknowledgement code is a business decision, not an HL7 one. The protocol is unambiguous; what is frequently ambiguous is what the receiver should do when processing fails halfway through.
4.1 AA — Application Accept
Send AA when:
- The message parsed cleanly.
- The business logic committed successfully (database insert, downstream publish, etc.).
- Any async post-processing is safely queued and guaranteed eventually to complete.
4.2 AE — Application Error
Send AE when:
- The message parsed but a downstream system is unavailable and the receiver cannot reliably queue the work.
- A database constraint was violated by the incoming data.
- A business-rule validation failed.
- An internal exception occurred mid-processing.
AE is a signal to the sender that the message was understood but could not be processed. Well-behaved senders will pause, alert, and retry with backoff. The receiver should populate ERR with enough context for a human to diagnose.
4.3 AR — Application Reject
Send AR when:
- The message is malformed (missing required segments, illegal data types).
- The sender is not authorized — wrong MSH-3/4 credentials, unexpected message type.
- The message type (MSH-9) is not supported by this receiver.
- The HL7 version (MSH-12) is not supported.
AR means "don't bother retrying — fix the message first". Senders should route AR responses to a dead-letter queue and alert rather than loop.
4.4 Decision table
| Situation | Code | Retry? |
|---|---|---|
| Committed to database | AA | No — move on |
| Queued for async processing with guarantee | AA | No |
| Downstream DB momentarily down | AE | Yes — with backoff |
| Business rule failure | AE | Investigate first |
| Missing MSH-10 | AR | No — fix message |
| Unsupported message type | AR | No |
| Unauthorized sender | AR | No — alert |
5. Original vs Enhanced Acknowledgement Mode
HL7 v2.4 introduced enhanced acknowledgement mode, which splits the commit step from the application step. Senders opt in via MSH-15 and MSH-16.
5.1 Original mode (default)
MSH-15 and MSH-16 empty — single ACK carries both meanings.
MSH|^~\&|EMR|HOSPITAL|LAB|HOSPITAL|20260421093000||ORU^R01|MSG00088|P|2.5
...
(processing completes)
<- MSA|AA|MSG000885.2 Enhanced mode
MSH-15 = AL (Always), NE (Never), ER (Error/reject only), SU (Successful only). Same values for MSH-16. Different fields cover the commit and application responses respectively.
MSH|^~\&|EMR|HOSPITAL|LAB|HOSPITAL|20260421093000||ORU^R01|MSG00088|P|2.5|||AL|AL
...
<- MSA|CA|MSG00088 ; Commit Accept — bytes received cleanly
(processing later completes)
<- MSA|AA|MSG00088 ; Application Accept — actually stored5.3 Enhanced codes
CA— Commit Accept: message framed and queued; application has not yet processed it.CE— Commit Error: message received but could not be queued (disk full, etc.).CR— Commit Reject: message refused before parsing (authentication, version mismatch).AA,AE,AR— application-level as before.
In practice: most production integrations still use original mode. Enhanced mode is valuable when you have long-running application processing and want safe pipelining, but both sides must explicitly support it — a misconfigured one-sided enablement usually means the sender never gets the ACK it expects.
6. Commit vs Application-Level ACK
The distinction between "bytes arrived" and "work completed" matters whenever receiver processing is slow, asynchronous, or can fail independently from message reception. Four cases:
- Bytes arrived, work committed — single AA in original mode, or CA+AA in enhanced mode.
- Bytes arrived, work still in progress — CA in enhanced mode; in original mode you must wait to ACK.
- Bytes arrived, work permanently failed — AE in original mode; CA then AE in enhanced mode.
- Bytes refused — AR in original mode; CR in enhanced mode.
Enhanced mode's big benefit is that the sender can learn "your bytes are safe with me" in milliseconds and move on, even if application processing takes seconds. In original mode, the sender is blocked waiting.
For high-throughput interfaces where receiver processing is bursty, enhanced mode materially improves pipelining and upstream stability.
7. Generating ACKs from Mirth Connect
Mirth's MLLP Listener has a Response configuration. By default, Mirth auto-generates a positive ACK mirroring MSH from the inbound message.
7.1 Default auto-ACK
Set Response → AUTO. Mirth emits:
MSH|^~\&|<inbound-MSH-5>|<inbound-MSH-6>|<inbound-MSH-3>|<inbound-MSH-4>|<timestamp>||ACK|<new-id>|<inbound-processing-id>|<inbound-version>
MSA|AA|<inbound-MSH-10>7.2 Custom response transformer
For conditional AA/AE, populate an ERR segment, or switch to enhanced mode, use JavaScript in the Response Transformer:
// Response Transformer — Mirth Connect
// Variables available: msg (inbound), response (ACK being built)
var controlId = msg['MSH']['MSH.10']['MSH.10.1'].toString();
var patientMRN = msg['PID']['PID.3']['PID.3.1'].toString();
if (!patientMRN) {
response = createResponse(
'AE',
controlId,
'PID-3 missing medical record number'
);
// Attach an ERR segment
var err = SerializerFactory.getSerializer('HL7V2').toXML(response);
// ...build ERR and serialize back
} else {
response = createResponse('AA', controlId, 'Message accepted');
}7.3 ACK Generator helper
Most teams wrap ACK construction in a code-template helper so every channel emits consistent responses:
// Code template: buildAck(inbound, code, textMessage, errors)
function buildAck(msg, code, text, errors) {
var ts = DateUtil.getCurrentDate('yyyyMMddHHmmss');
var ctrlId = msg['MSH']['MSH.10']['MSH.10.1'].toString();
var version = msg['MSH']['MSH.12']['MSH.12.1'].toString();
var msh = ['MSH', '^~\\&',
msg['MSH']['MSH.5']['MSH.5.1'].toString(),
msg['MSH']['MSH.6']['MSH.6.1'].toString(),
msg['MSH']['MSH.3']['MSH.3.1'].toString(),
msg['MSH']['MSH.4']['MSH.4.1'].toString(),
ts, '', 'ACK', UUIDGenerator.getUUID(), 'P', version].join('|');
var msa = ['MSA', code, ctrlId, text].join('|');
var lines = [msh, msa];
(errors || []).forEach(function (e) { lines.push(e); });
return lines.join('\r') + '\r';
}Reuse the helper in every channel's response transformer. See our Mirth Connect guide for the full pattern, and the heap space troubleshooting post for how expensive response transformers can cause memory pressure.
8. Reading ACKs from a Destination
When Mirth is the sender, the MLLP Sender connector reads the destination's ACK and exposes MSA-1 as the channel Response. A response transformer on the destination can inspect it.
// Destination Response Transformer
var ackMsg = SerializerFactory.getSerializer('HL7V2').toXML(responseStatus.getMessage());
var parsed = new XML(ackMsg);
var code = parsed.MSA['MSA.1']['MSA.1.1'].toString();
if (code === 'AA') {
responseStatus.setStatus(SENT);
} else if (code === 'AE') {
// Retryable
responseStatus.setStatus(QUEUED);
responseStatus.setStatusMessage('Receiver returned AE — will retry');
} else if (code === 'AR') {
// Not retryable — send to dead letter
responseStatus.setStatus(ERROR);
alerts.sendAlert('HL7 AR response', channelId, ackMsg);
}Treat AR as terminal and AE as retryable — then cap retries at 3-5 with exponential backoff. Without a cap, an intermittent downstream outage can generate hundreds of thousands of retries in minutes.
9. Retry Logic and Timeouts
ACK timeouts and retries are where ACK theory meets operational reality. Get them wrong and you produce duplicates, storms, or silent message loss.
9.1 ACK timeout
Configure the sender's ACK timeout above the receiver's 99th-percentile processing latency. If the receiver takes 3 seconds under load, a 5-second timeout is too tight; a 30-second timeout is safer.
Mirth default is 5 seconds. Override per channel:
Destination connector → MLLP Sender → Settings → Response Timeout: 30000 ms9.2 Retry strategy
- Initial retry — 1-2 seconds after failure.
- Exponential backoff — double each retry up to a cap (e.g., 5 minutes).
- Max retries — 3 to 5. Beyond that, quarantine.
- Dead-letter queue — persist quarantined messages for human review.
9.3 Idempotency at the receiver
Because retry storms produce duplicates, the receiver must be idempotent. Simplest approach: dedup on MSH-10 for N minutes.
// Receiver-side dedup cache
var cacheKey = 'hl7-dedup:' + controlId;
if (cache.get(cacheKey)) {
// Already processed — still ACK AA so sender moves on
return buildAck(msg, 'AA', 'Duplicate acknowledged');
}
cache.put(cacheKey, true, 3600); // 1 hour TTL
// ... process message ...The "ACK AA on dup" behavior is important — returning AE or AR would cause the sender to continue retrying even though the message was already safely stored.
10. Edge Cases and Vendor Quirks
10.1 MSH-10 mismatch
The most common failure mode. Receiver generates an ACK but puts the wrong value in MSA-2 (its own control ID, a timestamp, or an empty string). Sender never correlates and times out. Fix the response transformer to echo MSH-10 verbatim.
10.2 ACK after connection close
Some receivers close the MLLP connection immediately after writing the ACK. Well-behaved senders handle this; poorly-behaved senders throw a socket exception and mark the message as failed despite receiving the ACK. Prefer keeping the connection open.
10.3 Epic Interconnect peculiarities
Epic's Interconnect ACKs may contain vendor-specific Z-segments. Parsers strict about segment order can reject them. Use a lenient parser or strip Z-segments before downstream processing.
10.4 Cerner ACK correlation
Cerner CCL-generated ACKs occasionally include a trailing space or padding character before <FS>. Mirth handles this; older senders may not. If you see correlation failures only with Cerner endpoints, inspect the raw bytes.
10.5 Z-segments in ACKs
Some sites add Z-segments (ZMA, ZAK) to ACKs for local audit metadata. This is legal HL7, but not all receivers tolerate them. Test cross-vendor before deploying.
10.6 Batch (BHS/BTS) ACKs
HL7 batch protocol defines separate batch acknowledgements (using FHS/FTS). Most MLLP integrations avoid batches entirely — the one-message-one-ACK model is simpler and more reliable.
For more edge cases across the HL7 message catalog, see HL7 ADT messages reference and ORM/ORU lab workflow.
11. Production ACK Handling Checklist
- ✓Every MLLP listener generates an ACK for every message, even on error
- ✓MSA-2 always echoes MSH-10 of the triggering message
- ✓ERR segment populated for every AE/AR response with enough context to diagnose
- ✓Response transformers tested against at least AA, AE, and AR scenarios
- ✓ACK timeout calibrated to the receiver's 99th-percentile processing time
- ✓Retry counts capped (typically 3-5) with exponential backoff to avoid storms
- ✓Duplicate detection at the receiver — MSH-10 dedup keys in a short-TTL cache
- ✓Sender quarantine queue for messages that exhaust retries — never silently drop
- ✓Monitoring on AE rate — a spike indicates a downstream problem even when TCP is green
- ✓Enhanced mode disabled unless both sides explicitly support it
For a broader operational playbook, see common Mirth Connect issues & fixes, the common HL7 integration errors catalogue, and our integration services.
12. Frequently Asked Questions
What is the difference between an ACK and a NAK in HL7?
Strictly speaking, HL7 v2 does not use the term NAK — it uses three acknowledgement codes in the MSA-1 field: AA (Application Accept), AE (Application Error), and AR (Application Reject). In casual usage, engineers call AE and AR responses NAKs because they signal the sender that something went wrong.
What does MSA|AA mean?
MSA-1 equal to AA means Application Accept — the receiver parsed the HL7 message, processed it successfully, and the sender can mark it delivered. It is the HL7 equivalent of HTTP 200.
What does MSA|AE mean?
MSA-1 equal to AE means Application Error — the message was parsed but an error occurred while processing (database constraint, downstream system down, business-rule violation). The sender should investigate before retrying; blind retries will typically fail the same way.
What does MSA|AR mean?
MSA-1 equal to AR means Application Reject — the message is malformed, unauthorized, or of a type the receiver will not process. Do not retry AR responses without fixing the underlying problem; they indicate a structural issue the sender must address.
What is enhanced acknowledgement mode?
Enhanced mode separates the commit-level acknowledgement (the receiver confirming it framed and queued the message) from the application-level acknowledgement (the receiver confirming processing succeeded). MSA-1 uses CA/CE/CR for commit responses and AA/AE/AR for application responses. It is enabled per-message via MSH-15 and MSH-16.
Do I need to send an ERR segment with every AE?
HL7 v2.5 and later strongly recommend it — the ERR segment carries the specific field, error code, and human-readable reason. Many receivers skip it, but senders have a far easier time diagnosing failures when ERR is populated.
How does Mirth Connect generate ACKs automatically?
Each MLLP Listener has a response transformer. By default Mirth generates a positive ACK with MSH-derived values from the inbound message. You can override the transformer to emit custom ACK types, populate ERR, or switch to enhanced mode.
What ACK timeout should I configure?
5 to 30 seconds is typical for MLLP in production. Too short and you will generate false retries under load; too long and a stalled receiver ties up sender threads. Measure the receiver's 99th-percentile application processing time and set the timeout above that with a margin.
Can an ACK message be lost?
Yes — if the TCP connection drops after the receiver processed the message but before the ACK reached the sender, the sender will time out and retry. The receiver will see a duplicate. Idempotent processing at the receiver is the only reliable defense.
What is MSA-2?
MSA-2 is the Message Control ID being acknowledged — it must match MSH-10 of the original message. Mismatches break sender-side ACK correlation and are one of the most common root causes of "we sent the message, got an ACK, but the sender still retries" bugs.
Related Reading
- HL7 Integration: The Complete Guide
- Mirth Connect: The Complete Guide
- MLLP Protocol Explained
- HL7 v2 Message Structure Explained
- HL7 v2 vs v3 vs FHIR
- HL7 ADT Messages Reference
- HL7 ORM / ORU Lab Workflow
- Common HL7 Integration Errors
- Mirth Connect MLLP Connection Refused
- Common Mirth Connect Issues & Fixes
- EHR Integration Guide