Webhooks

Overview #

The eMed webhook system allows you to receive webhooks from events occurring within the eMed platform.

You can build workflows in your system, such as triggering email notifications or updating internal records in response to specific actions or changes within the eMed system.

To leverage the eMed webhook system’s capabilities, partners must implement an endpoint capable of accepting webhook calls. This endpoint must securely receive and process the JSON formatted messages outlined below. To use this functionality, please provide eMed with the URL of this endpoint for both the preproduction and production environments.

Message Format #

The webhook messages adhere to the CloudEvent 1.0.2 specification, ensuring a standardised format for event data interchange across services. It uses the structured HTTP binding format.

{
 "specversion": "1.0.2",
 "id": "/vQaJbQYInnYSuFn48rwUgv+AQLfkPsUof/7VXmtGPA=",
 "source": "https://embed-uk.preprod.babylontech.co.uk",
 "type": "fhir.appointment.v1",
 "data": {},
 "datacontenttype": "application/json",
 "dataschema": "http://hl7.org/fhir/StructureDefinition/Appointment",
 "time": "2024-04-25T16:21:42+00:00"
}
Field Description
specversion This field indicates the version of the CloudEvent specification being used.
id A unique identifier for the message, which is essential for tracing and handling the event across systems.
source Represents the URL or an identifiable component within the emitting system that generated the event.
type Describes the type of event related to the webhook call which helps in routing and processing the event correctly.
data Contains the payload of the message. {} would be replaced with JSON-formatted data that provides details about the specific FHIR R4 (Fast Healthcare Interoperability Resources) message. We use FHIR R4 rather than a custom format as it is a widely used standard in the industry and will help with partner system compatibility. This field may contain Patient Health Information (PHI).
datacontenttype Indicates the content type of the data field. “application/json” confirms that the data is formatted as JSON
dataschema Provides a URI that points to the schema defining the structure of the data object.
time The timestamp when the event was created, formatted according to ISO 8601. This time does not change during retries.

The eMed FHIR model in data contains various extensions and may include additional extensions and backward-compatible fields in the future to enhance its functionality.

Message Types #

The following event types can be sent depending on the specific needs of the partner:

Type Description Ressource
fhir.appointment.v1 It encompasses all updates related to the lifecycle of an appointment, from the time it is booked until it is completed. FHIR Appointment
fhir.patient.v1 It encompasses all updates related to a patient. FHIR Patient
fhir.referral.v1 It encompasses all updates related to patient medical referrals by clinicians. FHIR ServiceRequest

A webhook message is triggered every time there is a change to the FHIR model, such as the creation of a new patient or appointment, status changes, or updates to attributes.

Security #

To ensure the security of data transmission:

  • Partner endpoints must employ TLS with a valid certificate.
  • Each HTTP request will include an x-emed-signature header with the body’s HMAC SHA256 signature (hexadecimal encoding) using UTF-8 encoding, using a shared secret for authentication and integrity checks.
  • The x-emed-signature header can contain multiple, comma-separated signatures for additional security measures like key rotation.

Example of x-emed-signature header: #

x-emed-signature: HMAC_SHA256=1bd428cb33cf86eb7a8c41bb9505be124963f78153eb03da63ebf168c0cb8eb4,HMAC_SHA256=18679c249891d632562157a3a8cd6546868520c9f0312b498bf0425d4f906ea1

Example code to verify x-emed-signature header #

body = '{"specversion":"1.0.2","id":"d31NAhjCqcwWvSNkRTwO493+qiknbHq+olKW97db3o0=","source":"https://embed-uk.dev.babylontech.co.uk","type":"fhir.appointment.v1","data":{"resourceType":"Appointment","id":"581288","end":"2024-04-26T19:00:00.000Z","meta":{"profile":["https://fhir.bbl.health/StructureDefinition/BblAppointment"]},"start":"2024-04-26T18:50:00.000Z","status":"booked","extension":[{"url":"https://fhir.bbl.health/StructureDefinition/BblAppointmentMedium","valueString":"video"},{"url":"https://fhir.bbl.health/StructureDefinition/BblAppointmentPatientConsumerNetwork","valueUuid":"e1d25f26-e0c6-4c6c-85d8-c59a9e7daa5d"}],"identifier":[{"value":"581288","system":"https://appointment.bbl.health/Appointment"}],"reasonCode":[{"text":"Nesciunt et asperiores enim neque."}],"minutesDuration":10,"participant":[{"type":[{"coding":[{"code":"UOD_jb9LIK","system":"https://bbl.health","display":"Patient (person)"}]}],"actor":{"reference":"https://patient.bbl.health/Patient/a340ab36-e7ab-4d14-81d7-f4fd8de029b5"},"status":"accepted"},{"type":[{"coding":[{"code":"6Kw0F0JWWr","system":"https://bbl.health","display":"Healthcare professional (occupation)"}]}],"actor":{"reference":"https://clinician.bbl.health/Practitioner/b7305dba-228a-47a5-a682-5a6eb832c0c0"},"status":"accepted"},{"type":[{"coding":[{"code":"ADM","system":"http://terminology.hl7.org/CodeSystem/v3-ParticipationType","display":"admitter"}]}],"actor":{"reference":"https://membersupport.bbl.health/Practitioner/3dafba2a-c8ad-40db-8e41-03ffdf1b16c8"},"status":"accepted"}]},"datacontenttype":"application/json","dataschema":"http://hl7.org/fhir/StructureDefinition/Appointment","time":"2024-04-26T02:11:49+00:00"}'

hmac = OpenSSL::HMAC.hexdigest('SHA256', 'my-secret-key', body) # Change my-secret-key with the secret shared with eMed

http_signature_header = "HMAC_SHA256=#{hmac}"
# returns "HMAC_SHA256=2ce6b3afe2d1055956e8fea981a9d8d5cb6c1e292496ece524013e0f5480b35d"

Retry Strategy #

To manage non-200 HTTP responses or timeouts effectively, our system currently implements a structured retry strategy:

  • 1 second
  • 5 seconds
  • 10 seconds
  • 30 seconds
  • 1 minute
  • 5 minutes
  • 10 minutes

If the initial retries fail, the system will enter a periodic retry phase, attempting to resend the webhook every approximately 15 minutes for up to 24 hours. Please note that the exact timing and frequency of these retries are subject to change based on system performance and optimisation needs. This approach ensures that temporary issues with endpoint availability or connectivity do not lead to missed or unprocessed events. However, messages may be received out of the original order due to this strategy. Partners should utilise the time field to verify the creation time of each message.

Duplicate Message Handling #

In distributed systems, it’s possible to send identical messages multiple times. Partners are responsible for managing duplicates, either by ignoring messages with repeated identifiers or implementing an upsert strategy.

Appointment Messages #

Statuses #

The eMed system currently supports the following appointment statuses for webhook notifications: booked, pending, cancelled, noshow, and fulfilled. Each time an appointment transitions to one of these statuses, a webhook message is triggered and sent to the partner. While these are the primary statuses in use, it is advisable to handle all potential FHIR R4 statuses (proposed, arrived, entered-in-error, checked-in, waitlist, etc.) gracefully, as we may map to additional statuses in the future.

The booked status is particularly important for notifying patients about their appointments, and partners often use this status to trigger notifications and reminders. The pending status is used when upfront payment is required for an appointment; however, in cases where appointments are “free” (not paid by the patient upfront), the status will typically go directly to booked.

When an appointment is rescheduled, the original appointment is cancelled and a new appointment is created with a different appointment ID. Essentially, some details from the original appointment are copied to the new one, but they are treated as separate appointments with distinct IDs. Consequently, partners will receive a webhook notification for the appointment cancellation followed by a notification for the new appointment booking.

Please note that both start and end times are required fields in our system, regardless of the status.

Extracting the patient #

To extract the patient information from an appointment, you should filter the participant array for entries with the specific coding code "UOD_jb9LIK". This code identifies the participant as a patient within our system. Once the appropriate participant is found, you can retrieve the patient ID from the actor.reference URL, which follows the format https://patient.bbl.health/Patient/{patient_uuid}.

Here is a sample participant entry:

{
  "type": [
    {
      "coding": [
        {
          "code": "UOD_jb9LIK",
          "system": "https://bbl.health",
          "display": "Patient (person)"
        }
      ]
    }
  ],
  "actor": {
    "reference": "https://patient.bbl.health/Patient/f38d66fb-d0bf-4948-b817-c225c2d8b206"
  },
  "status": "accepted"
}

While it is technically possible to use coding.display == "Patient (person)" to find the patient reference, using the coding code "UOD_jb9LIK" and system "https://bbl.health" is more reliable, as this identifier is stable and specifically designed for this purpose.

There is a possibility that the patient message may arrive after the appointment message due to system outages or endpoint downtime. If you receive an appointment message before the corresponding patient message, you can handle this by returning a non-200 HTTP response. This approach ensures that the eMed system will retry sending the message, allowing time for the patient message to be received and processed first. This strategy ensures that all messages are correctly matched and processed in the appropriate order.

Example #

{
 "specversion": "1.0.2",
 "id": "/vQaJbQYInnYSuFn48rwUgv+AQLfkPsUof/7VXmtGPA=",
 "source": "https://embed-uk.preprod.babylontech.co.uk",
 "type": "fhir.appointment.v1",
 "data": {
   "resourceType": "Appointment",
   "id": "581283",
   "end": "2024-04-26T08:40:00.000Z",
   "meta": {
     "profile": [
       "https://fhir.bbl.health/StructureDefinition/BblAppointment"
     ]
   },
   "start": "2024-04-26T08:30:00.000Z",
   "status": "booked",
   "extension": [
     {
       "url": "https://fhir.bbl.health/StructureDefinition/BblAppointmentMedium",
       "valueString": "voice"
     },
     {
       "url": "https://fhir.bbl.health/StructureDefinition/BblAppointmentPatientConsumerNetwork",
       "valueUuid": "a6c69280-c50a-459f-a29b-8babac2dc714"
     }
   ],
   "identifier": [
     {
       "value": "581283",
       "system": "https://appointment.bbl.health/Appointment"
     }
   ],
   "reasonCode": [
     {
       "text": "I’m feeling unwell."
     }
   ],
   "minutesDuration": 10,
   "participant": [
     {
       "type": [
         {
           "coding": [
             {
               "code": "UOD_jb9LIK",
               "system": "https://bbl.health",
               "display": "Patient (person)"
             }
           ]
         }
       ],
       "actor": {
         "reference": "https://patient.bbl.health/Patient/f38d66fb-d0bf-4948-b817-c225c2d8b206"
       },
       "status": "accepted"
     },
     {
       "type": [
         {
           "coding": [
             {
               "code": "6Kw0F0JWWr",
               "system": "https://bbl.health",
               "display": "Healthcare professional (occupation)"
             }
           ]
         }
       ],
       "actor": {
         "reference": "https://clinician.bbl.health/Practitioner/7b28c785-b9bd-484b-94d6-64eeeb14c610"
       },
       "status": "accepted"
     },
     {
       "type": [
         {
           "coding": [
             {
               "code": "ADM",
               "system": "http://terminology.hl7.org/CodeSystem/v3-ParticipationType",
               "display": "admitter"
             }
           ]
         }
       ],
       "actor": {
         "reference": "https://membersupport.bbl.health/Practitioner/c2920235-7f8d-4461-abc4-ba25f301bf86"
       },
       "status": "accepted"
     }
   ]
 },
 "datacontenttype": "application/json",
 "dataschema": "http://hl7.org/fhir/StructureDefinition/Appointment",
 "time": "2024-04-25T16:21:42+00:00"
}

Patient Messages #

When a partner receives a "fhir.patient.v1" message for a new patient in the eMed system after authentication (see User provisioning and authentication), it’s essential to verify the patient’s identity and establish a linkage between the patient records in both systems. To accomplish this, the partner should compare at minimum the patient’s email address, full name, and date of birth provided during the authentication process (ID token) with the corresponding data in their system. If these details match exactly, the partner should save the eMed patient UUID along with their patient record for future reference. Subsequently, the same eMed patient UUID will be used in all future "fhir.patient.v1" messages pertaining to that patient, enabling consistent and synchronised data management between the partner’s system and the eMed platform.

Example #

{
 "specversion": "1.0.2",
 "id": "710ZA0QY1n_iBv1BSgtxDAv-8jjk-5P77TwnS7JQnxI",
 "source": "https://embed-uk.preprod.babylontech.co.uk",
 "type": "fhir.patient.v1",
 "data": {
   "resourceType": "Patient",
   "id": "a8644bcf-b077-43f5-a564-866dcdcc5f7c",
   "meta": {
     "profile": [
       "https://fhir.bbl.health/StructureDefinition/BblPatientProfile"
     ]
   },
   "name": [
     {
       "use": "usual",
       "text": "Test User",
       "given": [
         "Test"
       ],
       "family": "User"
     }
   ],
   "active": true,
   "gender": "female",
   "telecom": [
     {
       "value": "patient@example.com",
       "system": "email",
       "extension": [
         {
           "url": "https://fhir.bbl.health/StructureDefinition/BblContactInformationPrimary",
           "valueBoolean": true
         },
         {
           "url": "https://fhir.bbl.health/StructureDefinition/BblContactInformationStatus",
           "valueCode": "Unconfirmed"
         },
         {
           "url": "https://fhir.bbl.health/StructureDefinition/BblContactInformationSource",
           "valueString": "unknown legacy"
         },
         {
           "url": "https://fhir.bbl.health/StructureDefinition/BblCreatedAt",
           "valueDateTime": "2024-05-08T10:10:09.000Z"
         }
       ]
     },
     {
       "value": "+44 7909 090909",
       "system": "phone",
       "extension": [
         {
           "url": "https://fhir.bbl.health/StructureDefinition/BblContactInformationPrimary",
           "valueBoolean": true
         },
         {
           "url": "https://fhir.bbl.health/StructureDefinition/BblContactInformationStatus",
           "valueCode": "Unconfirmed"
         },
         {
           "url": "https://fhir.bbl.health/StructureDefinition/BblContactInformationSource",
           "valueString": "unknown legacy"
         },
         {
           "url": "https://fhir.bbl.health/StructureDefinition/BblCreatedAt",
           "valueDateTime": "2024-05-08T10:10:09.000Z"
         }
       ]
     }
   ],
   "address": [
     {
       "use": "home",
       "type": "both",
       "line": [
         "123 Fake Street",
         "Fake Town",
         "Fake City"
       ],
       "extension": [
         {
           "url": "https://fhir.bbl.health/StructureDefinition/BblContactInformationPrimary",
           "valueBoolean": true
         },
         {
           "url": "https://fhir.bbl.health/StructureDefinition/BblContactInformationStatus",
           "valueCode": "Unconfirmed"
         },
         {
           "url": "https://fhir.bbl.health/StructureDefinition/BblContactInformationSource",
           "valueString": "unknown legacy"
         },
         {
           "url": "https://fhir.bbl.health/StructureDefinition/BblCreatedAt",
           "valueDateTime": "2024-05-08T10:10:09.000Z"
         }
       ],
       "postalCode": "SW1A 1AA"
     }
   ],
   "birthDate": "1990-01-31",
   "identifier": [
     {
       "value": "a8644bcf-b077-43f5-a564-866dcdcc5f7c",
       "system": "https://patient.bbl.health/Patient"
     },
     {
       "value": "14429940",
       "system": "https://coreruby.bbl.health/Patient"
     }
   ]
 },
 "datacontenttype": "application/json",
 "dataschema": "http://hl7.org/fhir/StructureDefinition/Patient",
 "time": "2024-05-08T10:10:15+00:00"
}

Referral Meassges #

Example #

{
  "specversion": "1.0.2",
  "id": "h9plufGZbG_Zuu9l7WvvIwcD2WO1Xku9V4rE0iuwdqFg",
  "source": "https://embed-uk.preprod.babylontech.co.uk",
  "type": "fhir.referral.v1",
  "data": {
    "resourceType": "ServiceRequest",
    "id": "37248",
    "meta": {
      "profile": [
        "https://fhir.bbl.health/StructureDefinition/BblReferralServiceRequest"
      ]
    },
    "code": {
      "coding": [
        {
          "code": "sUL3lsffD3",
          "system": "https://bbl.health",
          "display": "Patient referral"
        }
      ]
    },
    "status": "active",
    "intent": "plan",
    "subject": {
      "reference": "https://patient.bbl.health/Patient/f1794f59-3cc8-4bcb-a9dd-1f8627c43f2d"
    },
    "priority": "routine",
    "extension": [
      {
        "url": "https://fhir.bbl.health/StructureDefinition/BblReferralServiceRequestReasonStartDate",
        "valueDateTime": "1970-01-01T01:00:00.000+01:00"
      },
      {
        "url": "https://fhir.bbl.health/StructureDefinition/BblReferralServiceRequestConsentForPreAuthorisation",
        "valueBoolean": false
      }
    ],
    "encounter": {
      "reference": "https://consultation.bbl.health/Encounter/ce2d2855-11f9-4ff0-8a93-ce73d172be3a"
    },
    "requester": {
      "reference": "https://clinician.bbl.health/Practitioner/f12e14e3-1ddb-4e12-83c9-cc5be18689d0"
    },
    "identifier": [
      {
        "value": "37248",
        "system": "https://referral.bbl.health/ServiceRequest"
      }
    ],
    "authoredOn": "2024-05-08T09:48:14.000+01:00",
    "performerType": {
      "coding": [
        {
          "code": "76",
          "system": "https://specialism.bbl.health/CodeSystem",
          "display": "Counselling"
        }
      ]
    }
  },
  "datacontenttype": "application/json",
  "dataschema": "http://hl7.org/fhir/StructureDefinition/ServiceRequest",
  "time": "2024-05-08T10:10:15+00:00"
}
Last modified on . Version 9548ff8.