For the complete documentation index, see llms.txt. This page is also available as Markdown.

Webhooks

How Vantage API delivers order and result updates to your endpoint: event families and payloads, HMAC signature verification, and retry semantics.

Overview

Webhooks are how Vantage API keeps you connected to the kit throughout the fulfilment and results process. Every state change is delivered as a signed HTTP POST to the HTTPS endpoint you registered (Account setup).

There are two event families, distinguished by the event_type field:

event_type

Scope

What it carries

order.status_changed

Order

Fulfilment progress: a status in the same order.* vocabulary REST uses, plus tracking_number once available

order_item.results_status_change

Order item

Results progress: a results_status in the results.* vocabulary, plus the test_taker

event_type tells you which family the event belongs to; the status / results_status field inside data tells you which state was reached. Route on event_type, then switch on the status field.

Every payload has the same envelope:

{
  "event_type": "order.status_changed",
  "event_id": "249956485092777984",
  "timestamp": 1763661470,
  "data": { }
}
  • event_id - unique per event, serialized as a JSON string (like all Vantage IDs). Delivery is at-least-once: deduplicate on event_id. It equals the X-Terra-Trace-Id header.

  • timestamp - Unix seconds when the event was sent.

Headers

Header
Description
Example

X-Terra-Signature

HMAC signature with timestamp

t=1763661470,v1=a1b2c3...

X-Terra-Trace-Id

Unique ID for debugging (matches the event_id)

249956485092777984

Content-Type

Always application/json

application/json

Signature verification

Webhooks are signed with HMAC-SHA256 using your Terra signing secret (the same signing secret shown in your Terra dashboard). Verify every delivery before trusting it.

The X-Terra-Signature header has the format:

  • t - Unix timestamp in seconds when the webhook was sent

  • v1 - hex-encoded HMAC-SHA256(signing_secret, "<t>.<raw_body>")

Verification steps:

  1. Parse t and v1 from the header.

  2. Reject if t is outside your tolerance window of now (5 minutes is a sensible default).

  3. Concatenate <t> + . + the raw, unaltered request body.

  4. Compute HMAC-SHA256 over that string with your signing secret.

  5. Compare to v1 with a constant-time comparison.

Delivery and retries

  • Respond with any 2xx status quickly (within 10 seconds); do heavy processing asynchronously.

  • On failure (network error, timeout, 408, 429, or any 5xx), the first delivery makes up to 5 HTTP attempts with exponential backoff and jitter (roughly 1s, 2s, 4s, 8s). If they all fail, the event is re-queued and redelivered as single attempts with growing delays (starting at 5s, doubling up to a 10-minute cap), up to 10 deliveries in total - at most ~14 calls spread over ~30 minutes - before the event is parked. Make your handler idempotent either way. Other 4xx responses are treated as a rejection and are not retried.

  • Parked (dead-lettered) events can be replayed by Terra - they are not silently lost.

  • Delivery is at-least-once and ordering is not guaranteed: deduplicate on event_id, and treat each event's status as the authoritative current state rather than assuming you saw every intermediate step.

You can inspect delivery outcomes (delivered, rejected, dead-lettered, attempt counts, final status codes) via GET /api/v1/webhook-deliveries - see Monitoring and debugging.

Event reference

order.status_changed

data fields: order_id (string), status, plus tracking_number and (sandbox only) supplier_item_id once available.

status

Meaning

order.payment_processing

Payment being processed (initial state of payment-gated orders; appears in the order response rather than as a webhook)

order.payment_complete

Payment confirmed

order.payment_failed

Payment failed

order.processing

Order accepted and being prepared by the supplier

order.delayed

Fulfilment delayed

order.delivery_fulfilled

Delivery details available - carries tracking_number

order.completed

Order complete

order.cancelled

Order cancelled

Example:

REST reads of the same order use the identical vocabulary - a webhook status can be matched verbatim against order_status or status_history.

order_item.results_status_change

data fields: order_id, order_item_id, variant_id (all strings), results_status, and a test_taker object (test_taker_id string, first_name, last_name, email, phone_number E.164 string). Depending on the status, also: failure_cause, escalation_level, acknowledgment_due_by.

results_status

Meaning

Extra fields

results.kit_activated

End user activated their kit (suppliers with an activation step only)

-

results.sample_processing_in_lab

Lab confirmed receipt of the sample

-

results.partial_results_ready

A subset of the panel has resulted

-

results.results_ready

Results available - fetch and acknowledge

-

results.sample_rejected

Lab rejected the sample

failure_cause (e.g. "blood contaminated")

results.lab_processing_error

Lab could not process the sample

-

results.escalation_raised

Clinical escalation on the result set

escalation_level, acknowledgment_due_by

(results.awaiting_sample is the initial per-item state set at order creation; it appears in the order response rather than as a webhook.)

Example - results ready:

Example - escalation raised:

escalation_level is one of not_escalated, very_low, low, medium, high, very_high. Escalations require prompt acknowledgment - see Acknowledging Results.

Last updated

Was this helpful?