MediRecords FHIR Implementation Guide
1.4.0 - release


Webhooks

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.

Webhook Event Specification

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.

Example Webhook Header

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",
                ...
              }
            }
          ]
        }
      }
    ]
  }
}

Webhook Event Types

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  

Webhook Endpoints

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.

Key Considerations

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.

Webhook Signatures

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

Verifying Signatures

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:

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

  2. Prepare the signed_payload string

    The signed_payload string is created by concatenating:

    • The timestamp (as a string)
    • The character “.” (full stop character)
    • The actual JSON payload (i.e., the request body)


    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", … }
       
    
  3. 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.

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

Preventing replay attacks

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.

Webhook Registration API

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 Webhook

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 Webhook

Get specific FHIR webhook URL using webhook id

Parameters    
webhook_id string(required) 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 Webhook

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 Webhook

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"
}

Webhook registration limitation

A maximum of 15 ENABLED webhook urls are allowed to be registered at any given time.