MediRecords FHIR Implementation Guide
20220926 - ci-build


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 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 to retrieve your endpoint’s secret from the webhooks endpoints.

MediRecords generates a unique secret key for each endpoint. 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=.

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

Patient

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  
Bundle
  Patient (1) 
  PractitionerRole (0..1) 
  Practitioner (0..1) 
  Organization (0..2)
     
patient.deleted Occurs whenever a patient record is deleted Example
Bundle
  request (1)
    url: DELETE Patient/{id}

Encounter

Hub Event Application Trigger Event  
encounter.created Occurs whenever a new encounter record is created Example
encounter.updated Occurs whenever an encounter record is updated  
Bundle  
  Encounter (1) 
  Patient (1) 
  PractitionerRole (0..1) 
  Practitioner (0..2) 
  Organization (0..2) 

AllergyIntolerance

Hub Event Application Trigger Event  
allergyintolerance.created Occurs whenever a new allergy record is created Example
allergyintolerance.updated Occurs whenever a allergy record is updated  
Bundle 
  AllergyIntolerance(1)
  Patient (1)
  PractitionerRole (0..1)
  Practitioner (0..2)
  Organization (0..1)
   
allergyintolerance.deleted Occurs whenever a allergy record is deleted
Bundle
  request (1) 
    url: DELETE AllergyIntolerance/{id}

Condition

Hub Event Application Trigger Event  
condition.created Occurs whenever a new condition record is created Example
condition.updated Occurs whenever a condition record is updated  
Bundle 
  Condition (1)
  Encounter (0..1)
  Patient (1)
  PractitionerRole (0..1)
  Practitioner (0..2)
  Organization (0..2)
   
condition.deleted Occurs whenever a condition record is deleted
Bundle 
  request (1) 
    url: DELETE Condition/{id}

Immunization

Hub Event Application Trigger Event  
immunization.created Occurs whenever a new immunization record is created Example
immunization.updated Occurs whenever a immunization record is updated  
Bundle 
  Immunization (1)
  Encounter (0..1) 
  Patient (1) 
  PractitionerRole (0..1) 
  Practitioner (0..4) 
  Organization (0..2) 
   
immunization.deleted Occurs whenever a immunization record is deleted
Bundle 
  request (1) 
    url: DELETE Immunization/{id}

Medication Request

Hub Event Application Trigger Event  
medication-request.created Occurs whenever a new prescription record is created Example
medication-request.updated Occurs whenever a prescription record is updated  
Bundle 
  MedicationRequest (1)
  Encounter (0..1)
  Patient (1)
  PractitionerRole (0..1)
  Practitioner (0..3)
  Organization (0..2)
   
medication-request.deleted Occurs whenever a prescription record is deleted
Bundle 
  request (1) 
    url: DELETE MedicationRequest/{id}

Diagnostic Report

Hub Event Application Trigger Event
diagnostic-report.created
coming soon
Occurs whenever a new diagnostic results record is created
diagnostic-report.updated
coming soon
Occurs whenever a diagnostic results record is updated
Bundle <br>
  DiagnosticReport(1) 
  Encounter (0..1) 
  Patient (1) 
  PractitionerRole (0..1)
  Practitioner (0..3)
  Organization (0..3)
   
diagnostic-report.deleted
Coming soon
Occurs whenever a diagnostic results record is deleted
Bundle 
  request (1) 
  url: DELETE DiagnosticReport/{id}

Document Reference

Hub Event Application Trigger Event
document-reference.created
coming soon
Occurs whenever a new correspondence record is created
document-reference.updated
coming soon
Occurs whenever a correspondence record is updated
Bundle<br>
  DocumentReference (1)
  Encounter (0..1)
  Patient (1)
  PractitionerRole (0..2)
  Practitioner (0..3)
  Organization (0..3)
   
document-reference.deleted
coming soon
Occurs whenever a correspondence record is deleted
Bundle<br>
  request (1)
    url: DELETE DocumentReference/{id}

Observation

Hub Event Application Trigger Event
observation.created Occurs whenever a new clinical observation record is created
observation.updated Occurs whenever a clinical observation record is updated
Bundle
  Observation (1..*)
  Encounter (0..1)
  Patient (1)
  PractitionerRole (0..1)
  Practitioner (0..2)
  Organization (0..2)
   
observation.deleted
coming soon
Occurs whenever a clinical observation record is deleted
Bundle<br>
  request (1)
    url: DELETE Observation/{id}

Family Member History

Hub Event Application Trigger Event  
family-member-history.created
coming soon
Occurs whenever a new family history record is created Example
family-member-history.updated
coming soon
Occurs whenever a family history record is updated  
Bundle
  FamilyMemberHistory (1)
  Patient (1)
  PractitionerRole (0..1)
  Practitioner (0..1)
  Organization (0..1)
   
family-member-history.deleted
coming soon
Occurs whenever a family history record is deleted
Bundle<br>
  request (1)
    url: DELETE FamilyMemberHistory/{id}

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
POST {API_URL}/webhooks
Authorization: Bearer {API_KEY}

{
  "url": "https://example.com/customer/webhook"
}

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.