A practice admin creates a subscription in Settings → Webhooks. The signing secret is shown once. The subscription belongs to that admin's practice. Events for another practice are not delivered to it.
Delivery is at most once. A retry path exists for contract 1.8.0: a stable event id, the same body bytes on every attempt, and HMAC of those bytes. It is not active. A failed partner webhook is not retried until that path is turned on for a clinic. When it is on, the stored body includes id, retries send those exact bytes, and X-Webhook-Id repeats that id. Dedupe on it. The body sent while the path is off is JSON:
{
"event": "appointment.created",
"timestamp": "2026-11-01T12:00:00.000Z",
"data": {}
}timestamp is the send time. The body does not include a stable event id. Receivers should treat delivery as at most once.
Contract 1.8.0 adds client.updated. It is sent only when a partner calls PATCH /api/v1/clients/{id}. Staff screens and other integrations update a client through their own writers, and those writes do not emit the event. There is no shared client-update function to attach it to. This public reference omits the event while the released contract is 1.7.0.
Headers are X-Webhook-Event and X-Webhook-Signature. The signature is hex HMAC-SHA256 of the raw body, using the subscription secret. Verify the raw bytes before parsing JSON.
Events
appointment.created— A new appointment was booked.appointment.updated— Appointment details were changed.appointment.status_changed— Appointment status transitioned.patient.created— A new patient record was created.patient.updated— Patient details were updated.client.created— A new client was registered.invoice.created— An invoice was generated.invoice.paid— An invoice was marked as paid.prescription.created— A prescription was issued.vaccination.recorded— A vaccination was recorded.policy.updated— Practice policy settings were changed.clinic.profile_updated— Practice profile details were updated.