Webhook Payload
Webhook delivery format, complete payload example, and field specifications.
⚠️ Pre-Release
This page is currently under active development and is provided in a pre-release state.
Some content may evolve as we continue to iterate on the implementation based on your feedback, but the core concepts and functionality are expected to remain consistent.
The page will be marked as general release in the near future
How Webhooks Work
When a lab report session reaches the sent status, Terra delivers an event to your configured webhook destination.
Configure your webhook URL in the Terra Dashboard
Upload a lab report via the API
Terra processes the report and delivers the event via HTTP POST to your endpoint
Your endpoint should return a
2xxstatus code to acknowledge receipt
Every webhook is an event envelope: event metadata (type, event_id, occurred_at, upload_id) wraps the report itself under data:
{
"type": "lab_report.completed",
"event_id": "evt_9f1c…",
"occurred_at": "2026-03-28T14:23:45Z",
"upload_id": "upl_4a2b…",
"data": { "session_id": "…", "panels": [ … ], "results": [ … ] }
}typeis the event:lab_report.completed(success) orlab_report.failed(failure). Branch on it in your handler.event_idis unique per event — use it to deduplicate redeliveries.upload_idties every event from the same upload together (one upload can yield more than one report).datais a focused event resource — the report's identity, metadata,panels, and standardizedresults. It is not the full session. For the full session (status history, file/byte counts, presigned files) callGET /v2/lab-reports/{session_id}and its sub-resources.
Result shape. Each result is grouped into four layers: source (what the report literally printed), biomarker (the normalized identity, key is null when unmatched), measurement (one typed value — check measurement.type and read the matching field), and interpretation (the coded abnormality signal). This is identical between the webhook payload and the GET response, so a result parses the same either way.
The webhook does not embed presigned file URLs or per-destination delivery state — those expire / change after delivery. Fetch them on demand from the sub-resources (/files, /deliveries) using the session_id from data.
Terra-Reference Header
Webhook requests include a Terra-Reference header containing the session ID. You can use this for logging and deduplication before parsing the body.
Complete Payload Example
Failure Webhook
If a report can't be processed — an unreadable file, an extraction or standardization error, or an internal error — Terra delivers a failure webhook to the same destination(s) instead of the success payload, so you're notified rather than left waiting.
Success and failure are distinguished by the envelope type: lab_report.completed for a completed report, lab_report.failed for a failure. Branch on type in your handler. A failure event is the same envelope; its data carries the session_id, reference_id, and an error object.
Error Object
code
string
Machine-readable failure code (see below)
message
string
Human-readable description of the failure
retriable
boolean
true if re-uploading the same report may succeed; false if it won't
Error Codes
file_unreadable
false
The uploaded file couldn't be read — upload a clearer scan/export.
extraction_failed
true
Biomarker extraction failed; a retry may succeed.
standardization_failed
true
Biomarker standardization failed; a retry may succeed.
internal
true
An internal error occurred; a retry may succeed.
Field Specifications
Event Envelope
type
string
lab_report.completed or lab_report.failed
event_id
string
Unique per event — deduplicate redeliveries on it
occurred_at
string
When the event occurred (ISO-8601, UTC)
upload_id
string
Correlates every event produced from the same upload
data
object
The report resource — the Data Object below (completed) or an error (failed)
Data Object
The data of a lab_report.completed event. This is a focused event resource — the report's identity, metadata, and standardized results — not the full session. For the full session (status history, file/byte counts, presigned files) call GET /v2/lab-reports/{session_id} and its sub-resources. Input files, the thumbnail, and per-destination delivery state are not here — fetch them on demand from the /files, /artifacts, and /deliveries sub-resources.
session_id
string
Snowflake int64 ID (as string) — always present
reference_id
string
Your external identifier (from upload query param)
report_date
string
Date from the lab report (YYYY-MM-DD)
report_time
string
Time from the lab report (HH:MM 24-hour)
report_locale
string
Locale of the report (e.g. "ja-JP", "pt-BR")
results_count
integer
Number of biomarker results — always present
panels
object[]
Report-level panels that results reference by panel_id (omitted if the report has no panel grouping)
results
object[]
Array of biomarker result objects — always present
report_notes
string
Free-text notes extracted from the report
client_id is intentionally omitted from the API response. The API authenticates by client identity, so echoing it back is redundant.
Panel Object
A panel is a report-level grouping (e.g. "CBC"). Results point at a panel by panel_id; join on it to group results under their panel.
id
integer
Report-local panel id — matches biomarker.panel_id on results
name
string
Verbatim panel header as printed on the report
key
string?
Normalized dedup key for the panel (a stable text key, not a LOINC code)
Result Object
Each result is grouped into four layers plus its reference ranges. source is what the report literally printed, biomarker is the normalized identity, measurement is the single typed value, and interpretation is the coded abnormality signal.
source — verbatim provenance:
name
string
Exact biomarker name as printed on the report
panel
string?
Verbatim panel header (omitted if the result isn't panelled)
value
string?
The value exactly as printed on the report
units
string?
Units as printed on the report
flag
string?
Raw flag text as printed (e.g. "H", "↑", "Négatif")
method
string?
Lab methodology (e.g. "ECLIA")
notes
string?
Additional lab comments
reference_text
string?
Human-readable reconstruction of the printed reference range
collection_date
string?
Specimen collection date for this result (YYYY-MM-DD)
collection_time
string?
Specimen collection time for this result (HH:MM 24-hour)
biomarker — normalized identity:
key
string | null
Canonical biomarker slug; always present, null when unmatched
display_name
string?
Standardized English name
loinc_code
string?
LOINC code (omitted if not mapped)
panel_id
integer?
References the panels[].id this result belongs to
panel_key
string?
Normalized panel key (see Panel Object)
specimen
string?
"blood", "serum", "plasma", "urine", "saliva", "stool", or "other"
measurement — one typed value. Read type, then the matching field:
type
string
"numeric", "bounded", "qualitative", "text", or "absent"
numeric
number?
Present when type is "numeric"
bounded
object?
Present when type is "bounded": { "operator": "lt"|"gt", "value": number }
qualitative
object?
Present when type is "qualitative": { "text": string }
text
string?
Present when type is "text"
absent_reason
string?
Present when type is "absent" (why the value is missing; may be null)
units
string?
Units of the numeric/bounded magnitude
ucum_code
string?
UCUM-compliant unit code
interpretation — the coded "is this abnormal" signal:
flag
string | null
Coded abnormality (see Interpretation Flag enum); null when there is no signal
flag_raw
string?
The lab's verbatim flag text the code was derived from
source
string
Where the flag came from: "report" (normalized from the printed flag) or "none"
applied_range
object?
The reference range the flag was judged against: { "lower": number?, "upper": number? } — populated when exactly one range applies to the patient
reference_ranges
object[]
Array of reference range objects
Reference Range Object
lower
number?
Lower limit (omitted for one-sided ranges like "< 4.0")
upper
number?
Upper limit (omitted for one-sided ranges like "> 1.0")
type
string
Range-type label — see enum values below
context
object?
Demographic context for when this range applies
Context Object
sex
string
"male", "female", or omitted (applies to all)
age_lower
integer?
Minimum age in years this range applies to
age_upper
integer?
Maximum age in years (omitted = no upper limit)
pregnancy_status
object?
Pregnancy status: "trimester_1", "trimester_2", or "trimester_3" (or omitted)
cycle_phase
object?
Menstrual cycle phase (or omitted)
gestational_week_lower
integer?
Lower gestational week bound (omitted if not applicable)
gestational_week_upper
integer?
Upper gestational week bound (omitted if not applicable)
reference_population
object?
Reference population (e.g. "nhanes_iii", "young_adult"; omitted if not set)
modifiers
string[]
Additional qualifiers (e.g. ["fasting", "supine"])
Enum Reference
Status
processing
Extracting biomarker data from the file
processed
Extraction complete
standardizing
Matching biomarkers and normalizing units
standardized
Standardization complete, results queryable
sending
Delivering payload to webhook
sent
Delivered to all configured destinations
partially_sent
Delivered to at least one destination, but failed others
failed
An error occurred
retry_scheduled
Retry has been scheduled
retrying
Retry in progress
cancelled
Processing was cancelled
deleted
Session has been deleted
Reference Range Type
Values for reference_ranges[].type — the label describes the range, not a verdict on the result.
normal
Within normal range
low
Below normal
high
Above normal
borderline_low
Borderline low
borderline_high
Borderline high
critical_low
Critically low
critical_high
Critically high
abnormal
Abnormal
therapeutic
Within therapeutic range
subtherapeutic
Below therapeutic range
toxic
Toxic level
Interpretation Flag
Values for interpretation.flag — the coded per-result abnormality signal (null when there is no signal).
normal
Not flagged
low
Below range
high
Above range
borderline_low
Borderline low
borderline_high
Borderline high
critical_low
Critically low
critical_high
Critically high
abnormal
Flagged, direction unspecified
Specimen Type
blood
Blood
serum
Serum
plasma
Plasma
urine
Urine
saliva
Saliva
stool
Stool
other
Other
Result Type
numeric
Numeric measurement
qualitative
Categorical outcome
text
Free-text observation
Biological Sex
male
Male
female
Female
Report Type
lab
Lab report
Last updated
Was this helpful?