Skip to main content
Guide

Webhooks

Medicus can notify your application of events by sending an HTTP POST to a URL you register. Each webhook is a signed JWT you can verify came from Medicus.

Events

EventSent when
record.openedA patient's care record is opened.
consultation.startedAn encounter/consultation is created.
callin.notification.shownAn appointment's arrival status is set to Called In.
callin.notification.removedA called-in appointment's consultation starts, its called-in status is manually cleared, or it's marked Did Not Attend.

Each event's data shape is specific to that event:

  • record.opened and consultation.started carry a patientId, plus userName and userEmail identifying who triggered it (both may be null if the acting user can't be determined, e.g. an application-restricted call with no act claim).
  • callin.notification.shown carries appointmentId, patientName, roomName (null if no room is assigned), siteId, siteName, and calledInAt (ISO 8601).
  • callin.notification.removed carries appointmentId, siteId, and removedAt (ISO 8601).

Delivery

  • Method: POST to your registered URL (must be https://).
  • Content-Type: application/jwt. The body is the raw JWT string, not JSON.
  • Timeout: ~10 seconds.
  • Best-effort: there is currently no automatic retry. Failed deliveries are logged on the Medicus side but not re-sent, so treat webhooks as hints to pull fresh data rather than a guaranteed event stream.

Respond quickly with a 2xx. Use the jti claim to de-duplicate if you ever receive the same event twice.

Payload

The JWT is signed with RS256 and decodes to:

{
"iss": "medicus",
"iat": <unix-timestamp>,
"exp": <unix-timestamp + 60>,
"jti": "550e8400-e29b-41d4-a716-446655440000",
"event": "record.opened",
"tenantId": "<tenant>",
"data": {
"patientId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"userName": "Dr Test Jones",
"userEmail": "gppartner@medicus.health"
}
}

The token is short-lived (exp is ~60s after iat).

A callin.notification.shown payload's data looks different, since it isn't scoped to a patient's care record:

{
"iss": "medicus",
"iat": 1767258000,
"exp": 1767258060,
"jti": "550e8400-e29b-41d4-a716-446655440000",
"event": "callin.notification.shown",
"tenantId": "<tenant>",
"data": {
"appointmentId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"patientName": "Jane Doe",
"roomName": "Room 1",
"siteId": "6c9b5f2a-1234-4c3e-9abc-1234567890ab",
"siteName": "Main Surgery",
"calledInAt": "2026-07-22T09:15:00+00:00"
}
}

Verifying a webhook

  1. Fetch Medicus's public keys from the tenant's JWKS endpoint. Cache the response, and only re-fetch when you receive a kid you don't recognise (this is the standard pattern for handling key rotation without hammering the endpoint on every webhook):
    • Staging: GET https://{tenantId}.api.staging.england.medicus.health/transactional-api/jwks
    • Production: GET https://{tenantId}.api.england.medicus.health/transactional-api/jwks
  2. Verify the JWT signature (RS256) against the key whose kid matches the token header.
  3. Check iss is medicus and that exp has not passed.
  4. Process event + data.

Configuration

Webhook destination URLs are configured per application and per environment when your application is onboarded with Medicus. You only receive an event if a URL is registered for it and your application is enabled for the tenant.