How to retrieve Quest lab results programmatically
With LabLink, Quest results are pushed to you rather than pulled. When results are posted to an order, LabLink sends a signed order.results_final webhook to your endpoint. It carries Quest's own results message, HL7 v2 ORU^R01 parsed to JSON without interpretation, and the PDF report.
GET /orders/{id} tells you when an order has reached results_final, but the results themselves arrive in the event: no API endpoint returns them. This guide covers the event, how to read the HL7 JSON, corrected results, and handling results safely.
How results reach you
- You place the order with
POST /orders. Its status iscreated. - When Quest has the requisition, LabLink sends
order.awaiting_collection. Download the requisition PDF the patient brings to the draw withGET /orders/{id}/requisition. - The patient is drawn and Quest runs the tests.
- When results are posted, LabLink sends
order.results_finalwith the results message and PDF report, and the order's status becomesresults_final.
The order.results_final event
| Field | What it holds |
|---|---|
| id | The event id. The same order and event always have the same id: dedupe on it. |
| type | order.results_final |
| environment | sandbox or production |
| test | true for a test event sent from the console |
| data.orderId | The order. Read it with GET /orders/{id}. |
| data.results.format | Always hl7v2+json |
| data.results.messages | Quest's results message, every segment in order, every field under its HL7 position. Usually one; more when the lab network holds several for the order. |
| data.results.reports | The PDF report: filename, contentType, base64, and source (hl7 when Quest embedded it in the message, lab_network otherwise) |
A shortened sandbox test event, with the base64 cut short:
{
"id": "evt_8a1d5c3e9b7f2a6d4c0e1b9f7a3d5c2e",
"object": "event",
"type": "order.results_final",
"environment": "sandbox",
"createdAt": "2026-10-21T15:06:12.000Z",
"test": true,
"data": {
"orderId": "0",
"status": "results_final",
"results": {
"format": "hl7v2+json",
"messages": [
{
"encoding": { "field": "|", "component": "^", "repetition": "~", "escape": "\\", "subcomponent": "&" },
"segments": [
{ "segment": "MSH", "fields": { "3": "LAB", "4": "QUEST DIAGNOSTICS", "9": ["ORU", "R01"], "12": "2.5.1" } },
{ "segment": "PID", "fields": { "1": "1", "5": ["SAMPLE", "TEST"], "7": "19000101", "8": "U" } },
{ "segment": "OBR", "fields": { "1": "1", "4": ["91986", "DONOR, CHAGAS SCREEN", "99QDI"], "25": "F" } },
{ "segment": "OBX", "fields": { "1": "1", "2": "ST", "3": ["86011607", "DONOR, CHAGAS SCREEN", "99QDI"], "5": "NONREACTIVE", "7": "NONREACTIVE", "8": "N", "11": "F" } },
{ "segment": "NTE", "fields": { "1": "1", "2": "L", "3": "This is a test event. No patient or order exists." } },
{ "segment": "OBX", "fields": { "1": "2", "2": "ED", "3": ["ClinicalPDFReport1", "Clinical PDF Report TEST000000-1", "99QDI"], "11": "F" } }
]
}
],
"reports": [
{ "filename": "ClinicalPDFReport1.pdf", "contentType": "application/pdf", "source": "hl7", "base64": "JVBERi0xLjQK" }
]
}
}
}
Reading the HL7 JSON
- Each message is
{ encoding, segments }, with segments in the order Quest sent them. - Fields are keyed by HL7 position, 1-based:
fields["5"]is OBX-5. Empty fields are left out. - A plain field is a string. A field with components is an array of them, and a component with subcomponents is an array inside it. A repeating field is
{ "repetitions": [ … ] }. - Observations are OBX segments under their OBR: OBX-3 the test, OBX-5 the value, OBX-6 units, OBX-7 the reference range, OBX-8 abnormal flags, OBX-11 the result status (
Ffinal,Ccorrected). NTE segments after an OBX are its comments. - Values are Quest's text, unchanged. LabLink does not interpret, normalize or re-code results.
A starting point for turning the event into rows. It skips the embedded PDF (OBX type ED), which is also in reports:
function observations(event) {
const rows = [];
for (const message of event.data.results.messages) {
let orderedTest = null;
let last = null;
for (const { segment, fields } of message.segments) {
if (segment === 'OBR') orderedTest = fields['4'];
if (segment === 'OBX' && fields['2'] !== 'ED') {
last = {
orderedTest,
observation: fields['3'],
value: fields['5'],
units: fields['6'],
referenceRange: fields['7'],
abnormalFlags: fields['8'],
status: fields['11'], // F final, C corrected
comments: []
};
rows.push(last);
}
if (segment === 'NTE' && last) last.comments.push(fields['3']);
}
}
return rows;
}
Delivery rules
- Register endpoints on the console's API keys & webhooks page, separately for sandbox and production, each with its own signing secret.
- Verify
LabLink-Signature(t=<unix seconds>,v1=<hex>, HMAC-SHA256 of<t>.<raw body>) before trusting an event, and reject one older than five minutes. The first sandbox order walkthrough has a Node.js check. - Answer 2xx within 10 seconds, then do the work. Redirects are not followed.
- A failed delivery is retried every 5 minutes, up to 3 more times. Delivery is at least once: the same event can arrive twice, so dedupe on
id. - A results event can be a few megabytes with its PDF. Accept bodies of that size.
- Events are sent for orders created through the LabLink API or the console playground.
Handling results safely
- The results event is a patient record: Quest's own message, with the patient's details. Keep it out of logs, analytics and error trackers, and store it as you would any PHI.
- No API endpoint returns results, so store them when the event arrives. Patient data is retained under the organization that created it, and CareGap never sells it.
- A corrected result is a new message from Quest and a new event with a new
id. Keep both, and show the latest. - Interpreting results and following up with patients stays with your ordering provider.
Questions
Can I poll for results instead of using webhooks?
You can poll GET /orders/{id} to see when an order reaches results_final, but the results themselves are delivered in the order.results_final webhook. Register a webhook endpoint to receive them.
What happens when Quest corrects a result?
A corrected result is a new message from Quest, so it arrives as a new order.results_final event with a new id. Its OBX-11 result status is C. The same message delivered twice keeps the same id.
Can I fetch results again later through the API?
Not today. No API endpoint returns results: they are delivered in the order.results_final event, so store them when it arrives, as you would any patient record.
Can I test results handling in sandbox?
Yes. Register a sandbox webhook endpoint and send a test event from the LabLink console. Test events have test set to true and an orderId of 0.
Next step
Read the webhook reference, then register a sandbox endpoint and send yourself a test results event.