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
| Event | Sent when |
|---|---|
record.opened | A patient's care record is opened. |
consultation.started | An encounter/consultation is created. |
callin.notification.shown | An appointment's arrival status is set to Called In. |
callin.notification.removed | A 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.openedandconsultation.startedcarry apatientId, plususerNameanduserEmailidentifying who triggered it (both may benullif the acting user can't be determined, e.g. an application-restricted call with noactclaim).callin.notification.showncarriesappointmentId,patientName,roomName(nullif no room is assigned),siteId,siteName, andcalledInAt(ISO 8601).callin.notification.removedcarriesappointmentId,siteId, andremovedAt(ISO 8601).
Delivery
- Method:
POSTto your registered URL (must behttps://). - 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
- Fetch Medicus's public keys from the tenant's JWKS endpoint. Cache the
response, and only re-fetch when you receive a
kidyou 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
- Staging:
- Verify the JWT signature (RS256) against the key whose
kidmatches the token header. - Check
issismedicusand thatexphas not passed. - 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.