Patient Activity Notifications

Get one webhook per patient when new data lands in a Zus record, then pull the changes with an incremental Group $export.

📘

Beta feature

Patient activity notifications are designed to work with Bulk FHIR Export, which is in private beta and may change or experience performance issues at any time.


1. Overview

Patient activity subscriptions notify your application when new data is added to a patient record visible to your builder. Instead of sending a webhook for every new resource, Zus sends one notification per patient per configured interval. Use the patient's Universal Patient Identifier (UPID) from the notification to run an incremental Group $export.

Key behavior

  • One notification per patient per interval. The interval starts when the first new resource arrives. Additional writes during that interval do not reset the timer.
  • Any write counts. New or updated Conditions, Observations, documents, Encounters, medications, and other resources that carry the patient's UPID trigger a notification. Resources without a UPID, such as Practitioners and Organizations, do not.
  • No resource filters. Filters are not currently supported for this subscription type.
  • No clinical data in the webhook. The payload contains only the patient's upid and group reference.
  • Not a FHIR Subscription resource. Zus configures and manages patient activity subscriptions separately from FHIR Subscription resources.

Choosing a subscription type

Subscription typeFires onTrade-offs
Resource-level subscriptionA specific resource type, such as EncounterCan send several notifications for a single ingest. Misses updates that do not include that resource type.
Patient activity subscriptionAny resource type that carries the patient's UPIDOne notification per patient per interval. The webhook carries no resource, so you run $export to get the data.

Notification flow

  1. A resource for the patient is written in Zus.
  2. If no interval is open for that patient and subscription, Zus starts one.
  3. When the interval ends, Zus sends a POST to your webhook.
  4. Your application runs $export using the patient's saved _since bookmark.
  5. After a successful export, your application saves the manifest's transactionTime as the patient's next _since bookmark. See Sync with $export.

2. Prerequisites

ItemWhat is neededNotes
Zus builderA Zus builder in sandbox and productionThe subscription is scoped to the builder specified during setup.
API credentialsA machine-to-machine app client (client ID and secret)Tokens last 1 hour. Token requests are limited to 200 per hour per app client. See Creating App Clients.
Webhook endpointA public HTTPS URL that accepts POST with a JSON bodyHTTP is rejected at subscription time. Return any 2xx within 120 seconds.
Webhook credentialsBasic auth (username and password) or OAuth 2.0 client credentials (token URL, client ID, client secret)Unauthenticated endpoints are not allowed in sandbox or production.
Export pipelineA job that runs $export for a patient group with _since and stores a per-patient bookmarkNotifications identify patients. Use $export to retrieve their data.
Patient visibilityPatients must be visible to the builder the subscription is scoped toAlerts fire for any patient the builder can see, including patients it did not create.

Hosts

EnvironmentExport hostToken endpoint
Sandboxhttps://fqs.sandbox.zusapi.comhttps://auth.sandbox.zusapi.com/oauth/token
Productionhttps://fqs.zusapi.comhttps://auth.zusapi.com/oauth/token

Use your app client's credentials to get an access token from the token endpoint, and send it as Authorization: Bearer <token> on $export requests. You do not need access to the subscriptions API.

3. Request a subscription

Zus sets up subscriptions on request. Contact your Zus account manager and provide the following for each environment. Zus confirms when the subscription is active.

FieldWhat to provideNotes
Subscription nameA short name, such as patient-export-syncUse the same name when requesting a change or removal.
Notification intervalA duration, such as 30m or 1hMinimum 30 minutes. Notifications are grouped per patient.
suppressOwnWritestrue or falseWhen true, writes made by your own builder do not trigger notifications.
Webhook URLA public HTTPS endpoint for each environmentZus sends POST notifications to this address.
Authentication typeBasic or OAuth 2.0 client credentialsZus authenticates to your webhook with the chosen method.
Webhook credentialsBasic: username and password. OAuth 2.0: token URL, client ID, client secret, plus audience and scopes if neededShare these separately from the setup request.
Environment and builderSandbox or production, and the relevant builderRequest and test sandbox first, then coordinate production.
🚧

Share webhook passwords and OAuth client secrets with Zus over an approved secure channel. Never send them in plain-text email.

4. Receive and verify notifications

Zus sends a POST to your webhook for each patient with activity during the interval.

POST https://webhooks.example.com/zus/patient-activity
Content-Type: application/json
Authorization: Basic <base64(username:password)>

{
  "upid": "c4b1d2e9-6bb4-4df5-b5c2-7b4761ace72e",
  "group": "Group/c4b1d2e9-6bb4-4df5-b5c2-7b4761ace72e"
}

With OAuth 2.0, the header is Authorization: Bearer <token>. The body is the same for both methods.

FieldAlways presentMeaning
upidYesThe patient's Universal Patient Identifier. Every Zus resource for this patient carries this UPID.
groupYesThe same patient written as Group/<upid>, the identifier $export takes.

Ignore any unrecognized fields so your handler stays compatible with future additions.

Verify that the call came from Zus

Notifications are not signed. Verify the Authorization header on every request.

Auth typeWhat arrivesWhat to verify
BasicAuthorization: Basic <base64(username:password)>Compare the decoded username and password to the pair you registered, using a constant-time comparison.
OAuth 2.0Authorization: Bearer <token> issued by your token URLValidate the token as you would any client credentials token from your identity provider: signature, issuer, expiry, and the audience or scopes you asked Zus to request.

Return 401 for invalid credentials. Zus retries a 401 like any other non-2xx response.

Treat webhook input as untrusted. Validate that upid is a UUID before using it in an export URL, and authenticate the request before scheduling any work.

Timing

  • The alert is sent within about a minute of the interval closing. The interval starts at the first new resource for the patient, not the last.
  • There is no alert for a patient with no new data.

Rotate webhook credentials

Contact Zus and provide the replacement credentials over a secure channel. Zus confirms when the new credentials are active. Until then, and for a short time afterward, accept both the old and new credentials so in-flight and retried requests can complete.

5. Process notifications and sync with $export

Recommended handler

  1. Verify the Authorization header. Return 401 on failure.
  2. Parse the body and validate that upid is a UUID. Return 400 on failure.
  3. Enqueue an export for upid. If an export for that UPID is already queued or running, do not schedule a duplicate.
  4. Return 200 or 202 once the work is queued, well within the 120 second timeout. Zus ignores the response body.

Do not wait for $export to finish before responding.

Delivery semantics

BehaviorWhat Zus doesWhat your application should do
Duplicate alertsAt-least-once delivery. If a delivery times out or returns an error, even after your endpoint processed it, the same UPID can be sent again later.Deduplicate queued jobs by UPID.
OrderingNo ordering across patients. One patient's alerts are at least one interval apart.Do not infer sequence from arrival order.
New data during deliveryIf more data lands while an alert is in flight, a fresh window starts and another alert follows after the next interval.Nothing. The next export picks it up.
Failed delivery (non-2xx, timeout, connection error)Retries with an increasing backoff over several hours.Monitor and fix webhook errors. Retries stop after a successful delivery.
Repeated failuresAfter repeated failed attempts, Zus pauses notifications for that patient on that subscription until the issue is resolved.Contact Zus support with the subscription name, and run a full export for patients whose notifications may have been paused.

Sync with $export

For each notification, run POST /Group/{upid}/$export?_since=<bookmark> against the export host. See Bulk FHIR Export for parameters, polling, and downloading.

  • Keep a per-patient _since bookmark, independent of notifications. The notification has no change timestamp, and a notification can be delayed or retried.
  • Update the bookmark only after a successful export.
  • Save the manifest's transactionTime as the next bookmark, and send it as _since on the patient's next export. Do not use the time you started the export job.
  • Resources written in the 15 minutes before an export request are always included, so consecutive incremental exports overlap. Upsert by resource ID.
  • On 409 Conflict, an export is already running for this patient and builder. Continue with the job in the Content-Location header.

6. What exports will not show

A notification can arrive for a change that the export does not contain:

  • Restricted-access data. A resource with a specially regulated security label triggers a notification but is withheld from the export.
  • Binary resources. The export never emits Binary resources. Document content is delivered through DocumentReference.

A notification with no new export data can also be a duplicate that arrived after an earlier export already picked up the change.

7. Change or remove a subscription

Contact your Zus account manager with the environment, builder, subscription name, and the requested change. Share any new webhook credentials over an approved secure channel.

  • Interval changes apply to notification windows opened after the update. Windows already in progress keep the previous interval.
  • suppressOwnWrites changes apply to subsequent writes.
  • Webhook URL and credential changes take effect shortly after Zus applies them, including for retries. Zus confirms when the change is active.
  • Removal. Zus confirms when the subscription is removed. Notifications that were already pending may still arrive for a short time afterward.

8. Test in sandbox and go live

Sandbox test procedure

  1. Get a sandbox token with your sandbox app client. The audience is https://api.sandbox.zusapi.com.
  2. Send Zus the sandbox configuration from Request a subscription, with an interval of 30m and your sandbox webhook URL. Wait for Zus to confirm the subscription is active.
  3. Pick a sandbox patient visible to your builder and note its UPID.
  4. Write a resource for the patient through the sandbox FHIR API at https://api.sandbox.zusapi.com/fhir, for example POST /fhir/Observation. If suppressOwnWrites is true, your own writes will not trigger a notification. Coordinate with Zus to use a test configuration with that setting disabled, or ask Zus to load test data.
  5. Allow about 31 minutes for the first notification. Write a second resource during the same interval and confirm you receive only one notification for the patient.
  6. Run $export against https://fqs.sandbox.zusapi.com with _since set to a time before your writes, and confirm the new resources are in the output.
  7. Return a 500 from your endpoint for one alert, and confirm that Zus retries the delivery.

If you need a connectivity or authentication check first, ask Zus to help validate webhook delivery. Test your payload parser with a real patient activity notification, since a generic test payload may have a different format.

Go-live checklist

  • Production app client created and token flow working against https://auth.zusapi.com/oauth/token
  • Production webhook reachable over HTTPS and accepting the credentials shared with Zus
  • Handler verifies the Authorization header and validates the UPID
  • Handler returns 2xx within a few seconds and queues the export asynchronously
  • Export jobs coalesce by UPID, honor Retry-After, handle 409, and use per-patient bookmarks
  • Alerting on a sustained rate of non-2xx responses, so an outage is fixed before notifications are paused
  • Initial full export and per-patient bookmarks ready before Zus enables production notifications
  • Zus has confirmed the production subscription is active with the requested interval and suppressOwnWrites setting

9. Limits and FAQ

LimitValue
Minimum interval30 minutes
Webhook response timeout120 seconds
Delivery retriesIncreasing backoff over several hours, then notifications pause
Alert latency after the window closesAbout 1 minute
Token requests200 per hour per app client. Tokens last 1 hour. See Rate Limits.
Exports in flight per patient per builder1
Export output retention7 days. Download links are valid for 15 minutes.

Why did I get a notification but no new export data?

The change may have come from your own builder with suppressOwnWrites set to false. The resource may carry a specially regulated security label, which triggers a notification but is withheld from the export. Or the notification may be a duplicate that arrived after an export already processed the change.

Why didn't a patient with new data trigger a notification?

Check whether the patient is visible to your builder, whether suppressOwnWrites excluded the write, and whether the interval has ended. Repeated webhook failures can also pause a patient's notifications. Review your webhook logs and ask Zus to investigate if needed.

Can the webhook include the resource instead of requiring an export?

Not with patient activity subscriptions. For resource-level notifications, use a filtered resource subscription. Those send a notification per resource rather than once per patient per interval.

When does the interval start?

When the first qualifying resource is written. Additional resources during the interval do not extend it.

What happens when patient records are merged or split?

Notifications do not currently identify merges or splits, or map between UPIDs. After a merge, the resulting patient record may receive later notifications under its own UPID. A dedicated merge and split signal is on the roadmap.

Is this a FHIR Subscription resource?

No. Zus configures and manages patient activity subscriptions separately from FHIR Subscription resources. The webhook identifies the patient as Group/<upid> for use with $export.

Support

Contact your Zus account manager or [email protected] with the environment, subscription name, UPID, approximate notification time, and any relevant webhook response codes or error messages. Zus can investigate delivery attempts and resume paused notifications.

Related pages


Did this page help you?