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 typeWebhook type valueDocumentation
Transition Alerts (admission, discharge, transfer, death)com.particlehealth.api.v2.transitionalertsTransition Alerts
Discharge Summary Alerts (delivered as a Transition Alert with subject: "Discharge Summary Available")com.particlehealth.api.v2.transitionalertsDischarge Summary Alerts
New Encounter Alertscom.particlehealth.api.v2.encounterNew 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_typeDescription
new_providerA new organization OID appeared in the patient's provider map
last_seen_updatedAn 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,841ad9e7229cc5d1fa44c326160eaabcc38292f586ca43f443cd1fca64c717d7

Query 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 purpose

The 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

  1. Extract the signature (841ad9e7229cc5d1fa44c326160eaabcc38292f586ca43f443cd1fca64c717d7) from the x-ph-signature-256 header. Everything after the first comma is a list of one or more bare hex signatures.

  2. Isolate the timestamp value (1684152014) from the first element of the header, dropping the t= prefix.

  3. Take the raw bytes of the notification request body, before any JSON parsing.

  4. 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 format unix_timestamp.raw_request_body.

  5. Use your signature key (particle-sandbox-demo-key-0000000000000000) and the HMAC SHA-256 algorithm to compute the signature of that message, hex-encoded in lowercase.

  6. Compare your computed signature against each signature in the header using a constant-time comparison (hmac.compare_digest in Python, hmac.Equal in Go, crypto.timingSafeEqual in 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.

  7. 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-256 header 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's request.get_data(), or Go's io.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-256 header 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,bbec44b8fc6ba8e5477851a927537a266947877365613d8ce958f590f8d93b63

ADT 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,b43e610e0610c89e0f57f1417a2770b069484cb6821331d16d82ac5afb8382f3

Did this page help you?