POSTs to the webhook URL registered for your brand. Each delivery is one event. Payloads are thin: ids and the minimum needed to route, never message bodies, form answers, protocol text or health values. Fetch details through the API with the right token.
Envelope
data always has enrollment_id and your external_patient_id. The full list of events is below, and each event’s payload is in the Webhooks section of the API reference. You can limit the events sent to you when your webhook is registered; by default you get all of them.
On the AI-only plan, every alert that raises the risk level and every AI Coach escalation sends
clinical.flag_raised, even when the alert rule is set not to notify the brand: there is no coach to reach, so your prescriber is the human in the loop.
Verifying the signature
Every delivery carries three headers:
Step by step:
- Read the raw request body as bytes. Do not parse and re-serialize the JSON first; any change in whitespace breaks the signature.
- Read
TeraRx-Timestamp. Reject the delivery if it is more than 5 minutes from your clock (replay protection). - Build the signed payload: the timestamp, a period, then the raw body:
<timestamp>.<raw body>. - Compute
HMAC-SHA256(secret, signed_payload)as lowercase hex, using your webhook secret. - Split
TeraRx-Signatureon commas. Accept the delivery if anyv1=value matches, compared in constant time. - Dedupe on
TeraRx-Event-Id: a retry carries the same id. Store ids for at least 24 hours and answer200to a repeat without processing it again. - Answer
2xxquickly, then process. Do the work after you respond, or queue it.
Worked example
With the secretexample_webhook_secret_new, timestamp 1793023500 and this exact body:
TeraRx-Signature: v1=5e9f492e2cee549f0d1688a9d4a6d0505ea1687e5c293eb28a01bad16ee7aa2a. During a rotation where the old secret was example_webhook_secret_old, it is v1=5e9f492e2cee549f0d1688a9d4a6d0505ea1687e5c293eb28a01bad16ee7aa2a,v1=2e8d962ded17a921cbc548b968abc024e9fcae75fd8ff8a0f2044999b0898416.
Node
Python
Secret rotation
TeraRx issues a new secret on request. For the rotation window (24 hours by default) every delivery is signed with both secrets, new first. Deploy the new secret, confirm deliveries verify with it, and tell TeraRx to end the window; after that only the new signature is sent.Delivery and retries
- Answer with any
2xxwithin 10 seconds. Anything else, or no answer in time, is a failed attempt. - Failed attempts are retried with exponential backoff (about 30 s, 2 min, 10 min, 30 min, then hourly) for 24 hours. Each attempt is signed again with a fresh timestamp; the event id stays the same.
- After 24 hours the event is dead-lettered. Nothing is lost: replay it with
GET /v1/events?since=<last event id you processed>, which keeps 30 days of events in order. - Deliveries are not guaranteed to arrive in order. Use
created_at, or fetch the current state through the API, when order matters.
Clinical flag re-delivery
A flag must be acknowledged by itsack_due_at, which comes from the acknowledgement time agreed in your contract. When it passes, the flag stays open and TeraRx sends clinical.flag_redelivered with a new event id (so your dedupe does not swallow it), a new ack_due_at and a redelivery_count. This repeats until a prescriber acknowledges the flag with POST /v1/clinical-flags/{flag_id}/acknowledge. Route re-deliveries to whoever covers when the first prescriber does not answer.
