Webhook Event Notifications
What is a Notification?
Most interactions with the Particle Platform are synchronous. A user makes a request and we respond immediately. However, a subset of interactions with the Particle Platform are asynchronous. A user will submit a request and, after a variable length of time, Particle will complete the request and have a response for the user. This is prevalent in our patient query flows where the query completion status is pending and customers "poll" for the status of a query.
Notifications change this relationship. Instead of the user checking for status of an operation (e.g. FHIR® query completion), Particle will send a proactive notification to the customer letting them know what the new status is (e.g. a FHIR® query has finished processing).
Notifications are also a critical part of the Signal product offered by Particle. Clients register a cohort of patients to be monitored on an ongoing basis for any net new data available on the networks and/or for critical admission events (i.e., ADTs) at any of care facility within Particle's network. A notification is delivered when new data is available and/or an ADT event occurs for the patient.
Registering to receive Notifications
If you are interested in utilizing the Particle notifications service, please reach out to your Particle Health representative and we're happy to get you set up!
The first step toward getting set up with Particle Notifications is to configure a web service that can receive a notification webhook. After we register this callback URL in our systems, Particle will generate a signature key that is used to sign the Notification payloads.
Notifications are currently used for query status alerts for (1) any queries initiated by a customer; and (2) any queries that Particle has initiated and retrieved net new data from, as part of Signal Product. Notifications are also used for ADT alerts and AI output events (such as Snapshot summaries).
Query Status Notifications
When a query completes, the registered web service will receive a notification in the form of a JSON payload. You will receive the same query-complete notification payload whenever Particle has retrieved net new data for a patient that you've subscribed to Signal. An example of this payload can be found below:
{
"specversion": "1.0",
"id": "f834539f-a839-490b-80f9-b441cb9e435d",
"source": "api/notifications",
"type": "com.particlehealth.api.v2.query",
"subject": "CCDA Query Complete",
"datacontenttype": "application/json",
"time": "2023-05-15T12:00:14.694292853Z",
"data": {
"external_patient_id": "ext_patient_123",
"file_count": "2",
"particle_patient_id": "75c771c5-600e-4c4b-a4e9-f932fa2827aa",
"patient_id": "9557a65e-55b7-4a3b-b0a1-f4dd2df5a2f8",
"person_id": "75c771c5-600e-4c4b-a4e9-f932fa2827aa",
"purpose": "TREATMENT",
"query_id": "50ee4bd4-bd87-4e66-bab1-7e0309a9a656",
"status": "COMPLETE"
}
}ADT Notifications
When a new ADT alert is available for a patient, the registered web service will receive a notification in the form of a JSON payload. An example of this payload can be found below:
{
"specversion": "1.0",
"id": "b84def23-1a04-4ba8-91ac-ee6ed8940ab6",
"source": "api/notifications",
"type": "com.particlehealth.api.v2.hl7v2",
"datacontenttype": "application/json",
"time": "2022-10-26T15:48:56.137897511Z",
"data": {
"message_id": "4801fa0e-70c9-4f5c-94aa-c5eeea311a99",
"patient_id": "MER20022556611"
}
}AI Output Notifications
When an AI output (such as a Snapshot summary) is generated for a patient, the registered web service will receive a notification. The data.outputs array contains one entry per output generated, each with an output_type, status, and a unique ai_output_id. An example of this payload can be found below:
{
"specversion": "1.0",
"id": "005212ea-6c06-4166-b0cb-bc50f26fc0e2",
"source": "api/notifications",
"type": "com.particlehealth.api.v2.aioutput",
"subject": "AI Outputs completed (1 completed, 0 failed)",
"datacontenttype": "application/json",
"time": "2026-03-06T13:54:51.01592781Z",
"data": {
"outputs": [
{
"ai_output_id": "451681f3-4fa6-4530-8fd8-ed69455be7c5",
"output_type": "DISCHARGE_SUMMARY",
"status": "completed"
}
],
"particle_patient_id": "75c771c5-600e-4c4b-a4e9-f932fa2827aa",
"query_id": "50ee4bd4-bd87-4e66-bab1-7e0309a9a656"
}
}Signal Notifications
Particle Signal delivers alerts as webhook notifications using the same CloudEvents structure as other notification types. The following Signal notification types are available:
| Alert type | Webhook type value | Documentation |
|---|---|---|
| Transition Alerts (admission, discharge, transfer, death) | com.particlehealth.api.v2.transitionalerts | Transition Alerts |
Discharge Summary Alerts (delivered as a Transition Alert with subject: "Discharge Summary Available") | com.particlehealth.api.v2.transitionalerts | Discharge Summary Alerts |
| New Encounter Alerts | com.particlehealth.api.v2.encounter | New Encounter Alerts |
Provider Map Change Notifications
When a patient's provider map changes, the registered web service will receive a notification. The data.change_type field indicates whether a new provider organization appeared (new_provider) or an existing organization's last-seen date advanced (last_seen_updated). The data.provider object contains the same fields as a provider entry in the Patient Provider Map API.
{
"specversion": "1.0",
"id": "c3f1a2d4-89b0-4e3c-a1f2-d3e4b5c67890",
"source": "api/notifications",
"type": "com.particlehealth.api.v2.providermap",
"datacontenttype": "application/json",
"time": "2026-01-15T09:22:10.123456789Z",
"subject": "ac82d365-97f9-4111-92fa-3a2b92744b12",
"data": {
"particle_patient_id": "ac82d365-97f9-4111-92fa-3a2b92744b12",
"external_patient_id": "ext_patient_123",
"change_type": "new_provider",
"provider": {
"oid": "2.16.840.1.113883.3.789",
"organization_name": "Example Regional Medical Center",
"network": "CAREQUALITY",
"last_seen": "2026-01-15",
"active": true,
"type": "Practice",
"npi": "1234567890",
"address": "123 Health Ave",
"city": "Springfield",
"state": "IL",
"postal_code": "62701",
"lat": "39.799600",
"lng": "-89.644600",
"managing_organization_name": "Epic Systems",
"managing_organization_oid": "2.16.840.1.113883.3.123"
}
}
}Provider map change notifications are delivered for the following change_type values:
change_type | Description |
|---|---|
new_provider | A new organization OID appeared in the patient's provider map |
last_seen_updated | An existing organization's last_seen date has advanced |
To subscribe to provider map change notifications, create a notification with notification_type: "providermap" via the Management API. Provider map must be enabled for your project - reach out to your Particle Health representative to get set up.
HTTP Headers
Notifications are sent as HTTP Post requests. These requests will contain the following key headers:
Accept:[*/*]
Content-Type:[application/cloudevents+json]
X-Ph-Signature-256:[t=1684152014,841ad9e7229cc5d1fa44c326160eaabcc38292f586ca43f443cd1fca64c717d7]Particle Health Notifications follow the Cloud Events spec and therefore set the content-type header value to application/cloudevents+json
Notification Signatures
Particle Health Notifications are signed with a signature key generated during notification callback URL registration. This helps Notification consumers determine the validity of requests sent to the callback URL.
Our webhook signature has the following schema and follows the HMAC SHA-256 algorithm:
HTTP Header = x-ph-signature-256
HTTP Header Values = t=nnn,signature1,signature2
t=nnn represents the timestamp when the notification was created and signature1 is the signature generated using an unexpired signature key (provided by Particle). Additional signatures may be present if a signature key has been recently rotated. This allows a grace period during which the notification receiver may make changes to accommodate for the updated signature key.
Signature Verification Process (example)
The values below form a complete test vector. Copy them exactly as shown and your implementation should produce the signature in the header.
Signature key
particle-sandbox-demo-key-0000000000000000
This key is for documentation only - it is issued to no project and signs no real traffic. Your production key is delivered separately during callback URL registration.
x-ph-signature-256 header
t=1684152014,841ad9e7229cc5d1fa44c326160eaabcc38292f586ca43f443cd1fca64c717d7Query Complete notification body (566 bytes, byte-exact - this is the raw request body as sent on the wire)
{"specversion":"1.0","id":"f834539f-a839-490b-80f9-b441cb9e435d","source":"api/notifications","type":"com.particlehealth.api.v2.query","subject":"CCDA Query Complete","datacontenttype":"application/json","time":"2023-05-15T12:00:14.694292853Z","data":{"external_patient_id":"ext_patient_123","file_count":"2","particle_patient_id":"75c771c5-600e-4c4b-a4e9-f932fa2827aa","patient_id":"9557a65e-55b7-4a3b-b0a1-f4dd2df5a2f8","person_id":"75c771c5-600e-4c4b-a4e9-f932fa2827aa","purpose":"TREATMENT","query_id":"50ee4bd4-bd87-4e66-bab1-7e0309a9a656","status":"COMPLETE"}}
The body is published compact and unformatted on purposeThe signature covers the exact bytes Particle transmitted. Pretty-printing the JSON, reordering keys, or adding a trailing newline changes those bytes and produces a different signature.
Steps
-
Extract the signature (
841ad9e7229cc5d1fa44c326160eaabcc38292f586ca43f443cd1fca64c717d7) from thex-ph-signature-256header. Everything after the first comma is a list of one or more bare hex signatures. -
Isolate the timestamp value (
1684152014) from the first element of the header, dropping thet=prefix. -
Take the raw bytes of the notification request body, before any JSON parsing.
-
Build the signed message by joining the timestamp value from step 2 with the raw body from step 3, separated by a period (
.). The message follows the formatunix_timestamp.raw_request_body. -
Use your signature key (
particle-sandbox-demo-key-0000000000000000) and theHMAC SHA-256algorithm to compute the signature of that message, hex-encoded in lowercase. -
Compare your computed signature against each signature in the header using a constant-time comparison (
hmac.compare_digestin Python,hmac.Equalin Go,crypto.timingSafeEqualin Node) rather than==. The notification is authentic if any one of them matches - during a key rotation the header carries signatures from both the old and new keys. -
If a signature matches, the notification was sent by Particle Health and wasn't altered during transmission. If no signature matches, or the
x-ph-signature-256header is absent entirely, reject the request.
Implementation guidance
- Hash the raw body, not a re-serialized copy. Most web frameworks parse JSON bodies for you. Capture the raw bytes first (for example, Express's
express.raw(), Flask'srequest.get_data(), or Go'sio.ReadAll(r.Body)) and hash those. Round-tripping through a JSON parser reorders keys and changes whitespace, which changes the signature. - Parse the header defensively. Split on commas. The first element always carries the
t=prefix and must have it stripped before use; every element after it is a bare lowercase hex signature. Accept the request if any of them matches. - Reject missing signatures, not just wrong ones. A request with no
x-ph-signature-256header must be rejected the same way a mismatched one is. A verifier that only compares when a header is present accepts unsigned traffic.
Notifications samples
The C-CDA Query Complete vector is the worked example above. Two more follow, both signed with the same documentation key. Each body is byte-exact - copy them verbatim to smoke-test your verification code before you go live.
FHIR® Query Complete Notification
Identical to the C-CDA vector above except for the subject line, which reflects the endpoint the query was submitted to. Note that a one-word change to the body produces a completely different signature.
{"specversion":"1.0","id":"f834539f-a839-490b-80f9-b441cb9e435d","source":"api/notifications","type":"com.particlehealth.api.v2.query","subject":"FHIR Query Complete","datacontenttype":"application/json","time":"2023-05-15T12:00:14.694292853Z","data":{"external_patient_id":"ext_patient_123","file_count":"2","particle_patient_id":"75c771c5-600e-4c4b-a4e9-f932fa2827aa","patient_id":"9557a65e-55b7-4a3b-b0a1-f4dd2df5a2f8","person_id":"75c771c5-600e-4c4b-a4e9-f932fa2827aa","purpose":"TREATMENT","query_id":"50ee4bd4-bd87-4e66-bab1-7e0309a9a656","status":"COMPLETE"}}t=1684152014,bbec44b8fc6ba8e5477851a927537a266947877365613d8ce958f590f8d93b63ADT Message Notification
{"specversion":"1.0","id":"b84def23-1a04-4ba8-91ac-ee6ed8940ab6","source":"api/notifications","type":"com.particlehealth.api.v2.hl7v2","datacontenttype":"application/json","time":"2022-10-26T15:48:56.137897511Z","data":{"message_id":"4801fa0e-70c9-4f5c-94aa-c5eeea311a99","patient_id":"MER20022556611"}}t=1666799336,b43e610e0610c89e0f57f1417a2770b069484cb6821331d16d82ac5afb8382f3Updated 1 day ago

