Skip to content

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 be application/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

  1. 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.

  2. Compute the HMAC: Using the same key that the server used, compute the HMAC with SHA-256 of the data string.

  3. 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

verify-signature.js
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 usage
const 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 you
verifySignature(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:

EnvironmentIP Addresses
Staging / sandbox environment18.206.70.225,44.195.8.253
Production environment52.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’s external_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 typeConditions when triggered
patient.createdWhen the signUp, completeCheckout, or createClient mutations are called successfully
patient.updatedWhen the updateUser or updateClient mutations are called successfully
message.createdWhen a message (a Note object) is sent in a conversation
conversation_membership.createdWhen a user is added to a conversation
form_answer_group.createdWhen a Patient completes a Form (intake, program or charting)
requested_form_completion.createdWhen 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

  1. 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