You can configure webhook endpoints to be notified about events that happen in your MediRecords account.
Webhooks are used to notify your application of changes made in the MediRecords account. An event-driven approach ensures you can send the right notification to the right application at the time it occurs.
Webhooks are used to notify your application of changes made in the MediRecords account. An event-driven approach ensures you can send the right notification to the right application at the time it occurs.
Field | Optionality | Type | Description |
---|---|---|---|
timestamp | Required | string | ISO 8601-2 timestamp in UTC describing the time at which the event occurred. |
id | Required | string | This id SHALL be unique and maps to the GUID of the Event |
event | Required | object | The event data |
hub.topic | Required | string | This the GUID related to the subscription of the Webhook client |
hub.event | Required | string | The event that triggered this notification, taken from the list of events from the subscription request. |
context | Required | array | An array of named objects corresponding to the data associated with the triggered event. The array will contain only one named object, a FHIR Bundle. |
key | Required | string | The key represents the name of the object within the event context. The key is usually insignificant but will normally be the first part of the hub-event representing the focal resource. |
resource | Required | Bundle | FHIR collection Bundle containing entry items including the focal resource associated with the triggered event and relevant resources referenced within the focal resource. |
MediRecords server SHALL only return FHIR resources that the subscriber is authorised to receive with the FHIR API_KEY granted to the Webhook subscriber.
The following is an example of a Webhook Header that contains skeleton Bundle resource. Example Bundle resources will be provided below.
{
"id": "cdb2f028-5546-4k52-87a0-0648e9ded041",
"timestamp": "2021-12-17T01:43:30.14",
"event": {
"hub.topic": "fdb2f928-5546-4f52-87a0-0648e9ded065",
"hub.event": "patient.created",
"context": [
{
"key": "patient",
"resource": {
"resourceType": "Bundle",
"id": "278dccbd-3b82-4736-a2f2-9e79dcd85fa9",
"meta": {
"lastUpdated": "2021-12-17T01:43:30Z"
},
"type": "collection",
"entry": [
{
"fullUrl" : "urn:uuid:1e5cae20-85bf-11e8-b401-ffccd711ad9c",
"resource" : {
"resourceType" : "Patient",
...
}
},
{
"fullUrl" : "urn:uuid:6d6d7ce8-65c2-11e6-a20e-7b5deed40054",
"resource" : {
"resourceType" : "Practitioner",
...
}
},
{
"fullUrl" : "urn:uuid:ade47c8c-9eed-11ea-a6fa-3f7e286deed9",
"resource" : {
"resourceType" : "Organization",
...
}
}
]
}
}
]
}
}
This section lists the currently available Webhook event types.
Hub Event | Application Trigger Event | |
---|---|---|
patient.created | Occurs whenever a new patient record is created | Example |
patient.updated | Occurs whenever a patient record is updated | |
patient.deleted | Occurs whenever a patient record is deleted | Example |
encounter.created | Occurs whenever a new encounter record is created | Example |
encounter.updated | Occurs whenever an encounter record is updated | |
allergy-intolerance.created | Occurs whenever a new allergy record is created | Example |
allergy-intolerance.updated | Occurs whenever a allergy record is updated | |
allergy-intolerance.deleted | Occurs whenever a allergy record is deleted | |
condition.created | Occurs whenever a new condition record is created | Example |
condition.updated | Occurs whenever a condition record is updated | |
condition.deleted | Occurs whenever a condition record is deleted | |
medication-request.created | Occurs whenever a new prescription record is created | Example |
medication-request.updated | Occurs whenever a prescription record is updated | |
medication-request.deleted | Occurs whenever a prescription record is deleted | |
immunization.created | Occurs whenever a new immunization record is created | Example |
immunization.updated | Occurs whenever a immunization record is updated | |
immunization.deleted | Occurs whenever a immunization record is deleted | |
observation.created | Occurs whenever a new clinical observation record is created | Example Vital Signs Clinical Template Observations |
observation.updated | Occurs whenever a clinical observation record is updated | |
observation.deleted | Occurs whenever a clinical observation record is deleted | |
diagnostic-request.created | Occurs whenever a new diagnostic request record is created | Example |
diagnostic-request.updated | Occurs whenever a diagnostic request record is updated | |
diagnostic-request.deleted | Occurs whenever a diagnostic request record is deleted | |
diagnostic-report.created | Occurs whenever a new diagnostic report record is created | Example |
diagnostic-report.updated | Occurs whenever a diagnostic report record is updated | |
diagnostic-report.deleted | Occurs whenever a diagnostic report record is deleted | |
document-in-reference.created document-out-reference.created |
Occurs whenever a new correspondence record is created | Example Letter in Example Letter out |
document-in-reference.updated document-out-reference.updated |
Occurs whenever a correspondence record is updated | |
document-in-reference.deleted document-out-reference.deleted |
Occurs whenever a correspondence record is deleted | |
document-reference.created | Occurs whenever an documentreference prescription is created | |
document-reference-consultation.created | Occurs whenever an documentreference consultation is created | |
document-reference-consultation.updated | Occurs whenever an documentreference consultation is updated | |
document-reference-attachment.create | Occurs whenever an documentreference consultation attachment record is created | |
episode-of-care.created | Occurs whenever an episode of care record is created | Example |
episode-of-care.updated | Occurs whenever an episode of care record is updated | |
episode-of-care.deleted | Occurs whenever an episode of care record is deleted | |
encounter-admission.created | Occurs whenever a new admissions encounter record is created | |
encounter-admission.updated | Occurs whenever an admissions encounter record is updated | |
admission.note.created | Occurs whenever a new admission note record is created | Example |
admission.note.updated | Occurs whenever a new admission note record is updated |
The first step to adding webhooks to your MediRecords integration is to build your own custom endpoint. Creating a webhook endpoint on your server is no different from creating any page on your website.
Before looking at the code, there are several key considerations regardless of the technology involved. You should also review the best practices for using webhooks.
For each event occurrence, MediRecords POSTs the webhook data to your endpoint in JSON format. The full event details are included and can be used directly after parsing the JSON into an Event object.
Return a 2XX status code quickly (define timeout)
To acknowledge receipt of an event, your endpoint must return a 2xx HTTP status code to MediRecords. All response codes outside this range, including 3xx codes, indicate to MediRecords that you did not receive the event.
If MediRecords does not receive a 2xx HTTP status code, the notification attempt is repeated. After multiple failures to send the notification over multiple days, MediRecords marks the event as failed and stops trying to send it to your endpoint. After 3 days without receiving any 2xx HTTP status code responses, MediRecords emails you about the misconfigured endpoint, and automatically disables your endpoint soon after if unaddressed.
Because properly acknowledging receipt of the webhook notification is so important, your endpoint should return a 2xx HTTP status code prior to any complex logic could cause a timeout.
Retry logic
MediRecords attempts to deliver your webhooks for up to three days with an exponential back off. Ideal retry timing: 15mins, 30mins, 1h, 2h, 4h, 8h then after every 8 hours until 3 days.
If your endpoint has been disabled or deleted when we attempt a retry, future retries of that event will be prevented. However, if you disable and then re-enable a webhook endpoint before we can retry, you should still expect to see future retry attempts.
Disable logic
MediRecords will attempt to notify you of a misconfigured endpoint via email if an endpoint has not responded with a 2xx HTTP status code for multiple days in a row. The email also states when the endpoint will be automatically disabled.
Handle duplicate event
Webhook endpoints might occasionally receive the same event more than once. In the future we will provide unique field event_id to handle duplicate event.
Order of events
MediRecords does not guarantee delivery of events in the order in which they are generated.
Receive events with an HTTPS server
You must use an HTTPS URL for your webhook endpoint, MediRecords will validate that the connection to your server is secure before sending your webhook data. For this to work, your server must be correctly configured to support HTTPS with a valid server certificate.
MediRecords will sign the webhook events it sends to your endpoints by including a signature in each event’s X-MediRecords-Signature header
. This allows you to verify that the events were sent by MediRecords, not by a third party.
Before you can verify signatures, you need your endpoint’s secret from the webhooks endpoints.
MediRecords generates a unique secret key for each endpoint on registration. Additionally, if you use multiple endpoints, you must obtain a secret for each one you want to verify signatures on. After this setup, MediRecords starts to sign each webhook it sends to the endpoint
The X-MediRecords-Signature
header included in each signed event contains a timestamp and one signature. The timestamp is prefixed by t=
, and the signature is prefixed by s=
.
Here is an example signature:
t=1668393631669, s=723b53d84484be47a82dd84a032fe6aab8004f039f1365aa8e7431990c69f5ef
MediRecords generates signatures using a hash-based message authentication code (HMAC) with SHA-256.
To verify webhook event signatures, you can follow these steps:
Extract the timestamp and signatures from the header
Split the header, using the comma(,) character as the separator, to get a list of elements. Then split each element, using the = character as the separator, to get a prefix and value pair. The value for the prefix t
corresponds to the timestamp, and s
corresponds to the signature. You can discard all other elements.
Prepare the signed_payload string
The signed_payload string is created by concatenating:
For example, signed_payload would look like this:
1668393631669.{{ "id": "b3e42976-3184-4894-b5c6-80ef2fb95907", "eventType": "patient.updated", "payload": { "timestamp": "2022-11-14T02:40:31.630706Z", "id": "b3e42976-3184-4894-b5c6-80ef2fb95907", "event": { "hub.topic": "2efc6b3e-72e0-46d0-88c1-f0485abc8fda", … }
Determine the expected signature
Compute an HMAC with the SHA256 hash function. Use the endpoint’s signing secret as the key and use the signed_payload string as the message.
Compare the signatures
Compare the signature in the header to the expected signature. For an equality match, compute the difference between the current timestamp and the received timestamp, then decide if the difference is within your tolerance.
A replay attack is when an attacker intercepts a valid payload and its signature, then re-transmits them. To mitigate such attacks, MediRecords includes a timestamp in the X-MediRecords-Signature
header. Because this timestamp is part of the signed payload, it is also verified by the signature, so an attacker cannot change the timestamp without invalidating the signature. If the signature is valid but the timestamp is too old, you can have your application reject the payload.
MediRecords generates the timestamp and signature each time an event is sent to your endpoint. If MediRecords retries an event, then a new signature and timestamp is generated for the new delivery attempt.
In order to receive FHIR Webhook Events, the Webhook Endpoint must be registered using the Webhook Registration API. The following sectiosn describe the Webhook Registration API.
Each request to the Webhook Registration API requires a FHIR API_KEY to be provided as a Bearer token in the HTTP request Authorization header. The API_KEY can be issued by MediRecords Admin user.
Create a FHIR Webhook URL
Headers | ||
---|---|---|
Authorization | string (required) | The FHIR API_KEY bearer token |
Body | ||
---|---|---|
url | string (required) | Supplied webhook url / |
event_types | array (optional) | webhook events |
POST {API_URL}/webhooks
Authorization: Bearer {API_KEY}
{
"url": "https://example.com/customer/webhook",
"event_types" : ["condition.created","immunization.deleted","encounter.updated"]
}
Response | ||
---|---|---|
webhook.id | string |
Webhook id. This is a unique identifier for this resource. This is a mandatory field in case of a PUT/UPDATE, GET and DELETE operations. Example: 138262b2-3e4d-11eb-9747-372b406ed24f |
webhook.url | string | Webhook URL |
webhook.status | string | Webhook Status |
webhook.createdDate | string | Time when the resource was created |
webhook.updatedDate | string | Time when the resource was updated |
secret | string | Generated secret string used for signing webhook payloads. You must save this secret if you intend to verify signatures. The secret cannot be retrieved by the READ endpoint. |
{
"webhook": {
"id": "2efc6b3e-72e0-46d0-88c1-f0485abc8fda",
"url": "https://webhook.site/0f92dd85-ea42-4dde-b96e-4c21210e3195",
"status": "ENABLED",
"createdDate": "2022-11-14T02:40:16.804Z",
"updatedDate": "2022-11-14T02:40:16.804Z"
},
"secret": "8hNTn9Yk0GJBgflUuAJFypwz2mPjpl78ozSgdSLTW9mHZxLTF76yxAOo7YM8CSNm2cu3ypPX9UiKChtG61HzaiEYK3pnomMiBbs89Tvc7mrcj4faXhRcNODyAZ9eZsiqkI35temQlXKEUqHJ9zkKrF732PCyQ5d40iiMORTwPiuqOKLPn7XnKRGTIrSlw1hjoiTyC9gZ7MWr7MUY9ahyJ6BHoADO96khvN8xHniqPwNYtJX3qYDHt8gV3Orc9st1"
}
Get specific FHIR webhook URL using webhook id
Parameters | ||
---|---|---|
webhook_id | string |
Webhook ID |
Headers | ||
---|---|---|
Authorization | string (required) | The FHIR API_KEY bearer token |
GET {API_URL}/webhooks/{webhook_id}
Authorization: Bearer {API_KEY}
Response | ||
---|---|---|
id: | string |
Webhook id. This is a unique identifier for this resource. This is a mandatory field in case of a PUT/UPDATE, GET and DELETE operations. Example: 138262b2-3e4d-11eb-9747-372b406ed24f |
status | string | Webhook Status |
url | string | Webhook URL |
createdDate | string | Time when the resource was created |
updatedDate | string | Time when the resource was updated |
{
"id": "138262b2-3e4d-11eb-9747-372b406ed24f",
"status": "ENABLED",
"createdDate": "2020-09-29T00:59:16Z",
"url": "https://example.com/customer/webhook",
"updatedDate": "2020-09-29T00:59:16Z"
}
Update a FHIR webhook URL
Parametes | ||
---|---|---|
webhook_id | string |
Webhook ID |
Headers | ||
---|---|---|
Authorization | string (required) | The FHIR API_KEY bearer token |
Body | ||
---|---|---|
url | string (required) | Supplied webhook url |
status | string (required) | Webhook Status ENABLED - Enable webhook DISABLED - Disable webhook |
PUT {API_URL}/webhooks/{webhook_id}
Authorization: Bearer {API_KEY}
{
"url": "https://example.com/webhook/new",
"status": "DISABLED"
}
Response | ||
---|---|---|
id | string |
Webhook id. This is a unique identifier for this resource. This is a mandatory field in case of a PUT/UPDATE, GET and DELETE operations. Example: 138262b2-3e4d-11eb-9747-372b406ed24f |
status | string | Webhook Status |
url | string | Webhook URL |
createdDate | string | Time when the resource was created |
updatedDate | string | Time when the resource was updated |
{
"id": "138262b2-3e4d-11eb-9747-372b406ed24f",
"status": "ENABLED",
"createdDate": "2020-09-29T00:59:16Z",
"url": "https://example.com/customer/webhook",
"updatedDate": "2020-09-30T00:59:16Z"
}
Delete a FHIR webhook URL
Parameters | ||
---|---|---|
webhook_id | string |
Webhook ID |
Headers | ||
---|---|---|
Authorization | string (required) | The FHIR API_KEY bearer token |
DELETE {API_URL}/webhooks/{webhook_id}
Authorization: Bearer {API_KEY}
Response | ||
---|---|---|
message | string |
{
"message": "Successfully Deleted"
}
A maximum of 15 ENABLED webhook urls are allowed to be registered at any given time.