Webhooks
Healthie’s API offers webhooks for common events.
When the event happens, Healthie will send a HTTP POST request with the following body to a URL you provide us.
You can see an example HTTP POST body in the right-hand column.
{ "resource_id": resource_id, # The ID of the resource that was affected "resource_id_type": resource_id_type, # The type of resource (can be 'Appointment', 'FormAnswerGroup', 'Entry', or 'Note') "event_type": event_type # The event that occurred}
We support different URLs for each event, as well as multiple URLs for the same event.
Retry Logic
You can choose to opt into having us retry a failed webhook if your service returns an error when we send a webhook to you. We will retry sending the webhook for up 3 days based on an exponential backoff. After retrying for up to 3 days, we will disable the webhook.
Email on Failure
If your service continues to return an error, we will send an email notification to let you know after approximately 24 hours.
Email on disable
If your service continues to return an error, we will disable the webhook after approximately 3 days.
Better URL validation
We have better validations around the URL of the service that you specify. You will be notified sooner if your URL does not validate.
Signed payloads
Overview
When you receive webhooks from our service, it is important to verify that they were indeed sent by us. This can be achieved by validating the signature in the webhook’s headers. Below, we provide the steps and a code sample for verifying the signature using JavaScript.
Signature Verification Process
Each webhook request includes several headers that are used to ensure the authenticity and integrity of the request:
Content-Type
: Specifies the media type of the resource (should beapplication/json
).Content-Digest
: Contains a SHA-256 hash of the request payload.Signature-Input
: Specifies the components used to construct the signature.Signature
: Contains the actual cryptographic signature of the message that you need to verify.
The signature is computed using HMAC (Hash-Based Message Authentication Code) with SHA-256 hashing. The data string used to generate the signature is constructed using specific parts of the request, including the HTTP method, path, query, and body content.
Steps to Verify the Signature
-
Construct the Data String: Replicate the construction of the data string that was signed on the server side. This involves concatenating the HTTP method, path, query string, content digest, content type, and content length of the request.
-
Compute the HMAC: Using the same key that the server used, compute the HMAC with SHA-256 of the data string.
-
Compare the Computed HMAC with the Signature in the Request: The signature provided in the webhook’s
Signature
header should match the HMAC you computed. If they match, the request is verified.
JavaScript Code Sample
async function getSigningKey(secretKey) { const encoder = new TextEncoder(); const keyData = encoder.encode(secretKey); return await crypto.subtle.importKey('raw', keyData, { name: 'HMAC', hash: { name: 'SHA-256' } }, false, ['sign']);}
function constructDataToSign({ method, path, query, headers, body }) { const contentDigest = headers['content-digest'].split('=')[1]; const contentType = 'application/json'; const contentLength = new Blob([JSON.stringify(body)]).size; return `${method.toLowerCase()} ${path} ${query} ${contentDigest} ${contentType} ${contentLength}`;}
async function generateSignature(key, data) { const encoder = new TextEncoder(); const signature = await crypto.subtle.sign('HMAC', key, encoder.encode(data)); return Array.from(new Uint8Array(signature)) .map(byte => byte.toString(16).padStart(2, '0')) .join('');}
async function verifySignature(requestData, secretKey) { const key = await getSigningKey(secretKey); const dataToSign = constructDataToSign(requestData); const computedSignature = await generateSignature(key, dataToSign); const actualSignature = requestData.headers['signature'].split('=')[1];
return computedSignature === actualSignature;}
// Example usageconst requestData = { method: 'post', path: '/uri/path/here', query: '', // any query params. leave blank if none body: { resource_id: 123123123, resource_id_type: 'Appointment', event_type: 'appointment.updated', changed_fields: ['pm_status_last_changed_by_id', 'last_updated_by_id'], }, headers: { 'content-type': 'application/json', 'content-digest': 'SHA-256=content-digest-from-header', signature: 'sig1=signature-from-header', },};
const secretKey = 'whsec_my-secret-key'; // The secret shared with youverifySignature(requestData, secretKey) .then(isValid => console.log('Is the signature valid?', isValid)) .catch(err => console.error('Error verifying signature:', err));
Thin payloads
When webhooks are sent for updates, we will include a changed_fields
property to the payload we send. This property will contain a list of fields that changed during the update.
Please note: For webhooks related to a created
event or deleted
event, we do not send data about the event other than the resource ID and event category. We expect you to make a GraphQL API call after receiving the event, using the resource ID, to get the data you need.
Security
In addition to signing our webhooks, you can whitelist Healthie’s current IP addresses as an additional security measure:
Environment | IP Addresses |
---|---|
Staging / sandbox environment | 18.206.70.225 ,44.195.8.253 |
Production environment | 52.4.158.130 ,3.216.152.234 |
Available Webhook Event Types
Below are the webhook event types available currently:
Type |
---|
applied_tag.created |
applied_tag.deleted |
appointment.created 1 |
appointment.deleted 1 |
appointment.updated 1 |
availability.created |
availability.deleted |
availability.updated |
billing_item.created |
billing_item.updated |
care_plan.created |
care_plan.deleted |
care_plan.updated |
cms1500.created |
cms1500.deleted |
cms1500.updated |
comment.created |
comment.deleted |
comment.updated |
conversation_membership.viewed |
document.created |
document.deleted |
document.updated |
dosespot_notification.created |
entry.created |
entry.deleted |
entry.updated |
form_answer_group.created |
form_answer_group.deleted |
form_answer_group.locked |
form_answer_group.signed |
goal.created |
goal.deleted |
goal.updated |
goal_history.created |
insurance_authorization.created |
insurance_authorization.deleted |
insurance_authorization.updated |
location.created |
location.deleted |
location.updated |
message.created |
message.deleted |
metric_entry.created |
metric_entry.deleted |
metric_entry.updated |
organization_info.created |
organization_info.deleted |
organization_info.updated |
other_id_number.created |
other_id_number.deleted |
other_id_number.updated |
patient.created |
patient.updated |
policy.created |
policy.deleted |
policy.updated |
received_fax.created |
requested_form_completion.created |
requested_form_completion.deleted |
requested_form_completion.updated |
requested_payment.created |
requested_payment.updated |
task.created |
task.deleted |
task.updated |
charting_note_addendum.created |
charting_note_addendum.updated |
charting_note_addendum.deleted |
completed_onboarding_item.created |
completed_onboarding_item.updated |
completed_onboarding_item.deleted |
conversation_membership.created |
conversation_membership.updated |
conversation_membership.deleted |
lab_order.created |
lab_order.updated |
lab_result.created |
medication.created |
medication.updated |
organization_membership.created |
organization_membership.updated |
request_form_creation.completed |
request_form_creation.updated |
request_form_creation.deleted |
Don’t see a webhook for an event you want to listen to? Please reach out to us.
Examples
Say you would like to add a special external video link when patients under a particular provider book an appointment. You could:
- listen to the
appointment.created
webhook event - fetch the appointment when the event is received
- if the appointment’s provider is a match, use the
updateAppointment
mutation to update the appointment’sexternal_videochat_url
You can find more examples how to leverage Healthie API Webhooks in the Automation Examples section.
When specific webhook events are triggered
Event type | Conditions when triggered |
---|---|
patient.created | When the signUp, completeCheckout, or createClient mutations are called successfully |
patient.updated | When the updateUser or updateClient mutations are called successfully |
message.created | When a message (a Note object) is sent in a conversation |
conversation_membership.created | When a user is added to a conversation |
form_answer_group.created | When a Patient completes a Form (intake, program or charting) |
requested_form_completion.created | When a Provider requests a Patient to complete a Form |
Webhook behavior in the context of sub-organizations
If your company is using sub-organizations in Healthie, you may want certain webhook events to fire only when they are triggered by an action in a given sub-org. Here is how webhook events behave in the context of parent organizations and sub-organizations:
- If the parent org has a webhook set up, all relevant events from all sub-orgs will be sent to that endpoint
- If a sub-org has a webhook set up, only relevant events from that sub-org will be sent to that endpoint
Footnotes
-
The appointment-related webhook events are only triggered for one-time appointments by default. If you also want to receive appointment-related events for recurring appointments, please reach out to the Healthie support team at hello@gethealthie.com and we’ll turn them on for you. ↩ ↩2 ↩3