Skip to content

Subscribing to Webhooks

Webhook delivery is managed through the Subscription Management API under base path /apis/v1/webhooks. A subscription is the unit of delivery: it ties one or more event types to a single delivery URL with its own signing secret.

Subscription Model

Each subscription has:

  • A human-readable name
  • One or more event_types (all delivered to the same endpoint) — see Event Types
  • Its own notification_url (delivery target)
  • Its own signature_key (HMAC secret — shown only at creation and rotation)
  • An api_version pinning the payload format (2026-06-11 is current)
  • An is_active toggle

One organisation can hold multiple subscriptions — e.g. one for patient.* events going to your EHR sync, another for appointment.* going to a scheduling integration, each with isolated credentials. You can also fan the same event out to several endpoints by creating multiple subscriptions with different notification_urls. You cannot register two active subscriptions that send the same event to the same notification_url — see Validation Rules.

Authentication

All /apis/v1/webhooks/* endpoints require a JWT Bearer token:

Authorization: Bearer JWT_TOKEN
Claim Required Used for
organisation_id Yes Identifies your organisation
user_id No Audit logging
name / user_name No Audit logging

The first time you call POST /apis/v1/webhooks/subscriptions with a new organisation_id, the organisation is auto-provisioned from the JWT claims — there is no separate registration step.

Subscription Endpoints

Method Path Description
POST /apis/v1/webhooks/subscriptions Create a subscription. Returns signature_key once.
GET /apis/v1/webhooks/subscriptions List your subscriptions. Optional ?is_active=true\|false.
GET /apis/v1/webhooks/subscriptions/:id Get a single subscription (signature_key hidden).
PATCH /apis/v1/webhooks/subscriptions/:id Update name, event_types, notification_url, api_version, or is_active. Replaces the whole event_types array.
POST /apis/v1/webhooks/subscriptions/:id/subscribe Add event_types to a subscription, keeping the existing ones.
POST /apis/v1/webhooks/subscriptions/:id/unsubscribe Remove specific event_types from a subscription, keeping the rest.
DELETE /apis/v1/webhooks/subscriptions/:id Delete the subscription.
POST /apis/v1/webhooks/subscriptions/:id/rotate-secret Generate a new signature_key. Returned once.
POST /apis/v1/webhooks/subscriptions/:id/test Send a synthetic webhook to your notification_url to confirm wiring.

Create a Subscription

POST /apis/v1/webhooks/subscriptions

Request Body

Parameter Type Required Description
name string Yes Human-readable subscription name. Non-empty.
event_types array Yes Non-empty array of event types. No duplicates.
notification_url string Yes Valid delivery URL. Must use https:// outside of development.
api_version string No Payload format version. Defaults to the latest (2026-06-11). Rejected with 400 INVALID_API_VERSION if unknown.

Request

curl --location '{base_url}/apis/v1/webhooks/subscriptions' \
  --header 'Authorization: Bearer JWT_TOKEN' \
  --header 'Content-Type: application/json' \
  --data '{
    "name": "Patient + appointment webhook",
    "event_types": ["patient.created", "patient.updated", "appointment.created"],
    "notification_url": "https://example.com/webhooks/spry",
    "api_version": "2026-06-11"
  }'

Success Response

Code: 201 Created

{
  "subscription": {
    "id": "773aaca1-4e36-4efc-b425-1201341a1712",
    "organisation_id": "3533728b-e152-4021-8e35-449adb9c0001",
    "name": "Patient + appointment webhook",
    "event_types": ["patient.created", "patient.updated", "appointment.created"],
    "notification_url": "https://example.com/webhooks/spry",
    "api_version": "2026-06-11",
    "is_active": true,
    "created_at": "2026-06-01T09:26:33.000Z",
    "updated_at": "2026-06-01T09:26:33.000Z"
  },
  "signature_key": "<128 hex chars>",
  "message": "Save this signature_key — it is used to verify webhook signatures and will not be shown again. Rotate via POST /subscriptions/:id/rotate-secret."
}

Response Fields

Field Type Description
subscription object The created subscription record.
subscription.id string Subscription identifier — use it on all /subscriptions/:id routes.
subscription.organisation_id string Owning organisation.
subscription.name string Subscription name.
subscription.event_types array Subscribed event types.
subscription.notification_url string Delivery target.
subscription.api_version string Pinned payload format version.
subscription.is_active boolean Whether deliveries are enabled.
subscription.created_at / updated_at string (date) ISO 8601 timestamps.
signature_key string HMAC secret for signature verification. Returned only at creation and on rotation — store it immediately.
message string Reminder that the key will not be shown again.

Error Response

Code: 400 Bad Request

{ "code": "INVALID_API_VERSION", "message": "Unsupported api_version", "data": null }

Validation Rules

  • name — required, non-empty.
  • event_types — required, non-empty array. Each value must be from Event Types. No duplicates.
  • notification_url — required, valid URL. Must use https:// outside of development.
  • api_version — optional. Must be a supported version; defaults to the latest. Rejected with 400 INVALID_API_VERSION if unknown.
  • No duplicate endpoint per event — if you already have an active subscription delivering one of these event_types to this exact notification_url, the request is rejected with 409 DUPLICATE_SUBSCRIPTION (the response names the conflicting subscription and the overlapping event(s)). Update the existing subscription, or point this one at a different URL to fan the event out to a second endpoint. The same check runs on PATCH when you change notification_url, event_types, or reactivate a subscription.

Managing a Subscription's Events

A subscription holds a set of event_types. There are two ways to change that set:

  • PATCH /subscriptions/:id with event_types replaces the whole list — whatever array you send becomes the complete set. Sending a partial list drops everything else. Use this when you want to set the exact set explicitly.
  • POST /subscriptions/:id/subscribe and POST /subscriptions/:id/unsubscribe change the set incrementally — add or remove just the events you name, leaving the rest untouched. Use these when you want to adjust one or two events without re-sending the whole array.

Each subscription is independent — changing one subscription's events never affects another.

Subscribe to More Events

Add one or more event types to an existing subscription, keeping its current events (and its notification_url / signature_key). Adding an event that's already present is a no-op.

POST /apis/v1/webhooks/subscriptions/:id/subscribe
curl --location '{base_url}/apis/v1/webhooks/subscriptions/773aaca1-4e36-4efc-b425-1201341a1712/subscribe' \
  --header 'Authorization: Bearer JWT_TOKEN' \
  --header 'Content-Type: application/json' \
  --data '{ "event_types": ["case.discharged"] }'

The response is the updated subscription. Rejected with 409 DUPLICATE_SUBSCRIPTION if an added event would collide with another active subscription delivering to the same notification_url.

Unsubscribe from Events

Remove one or more event types from a subscription while keeping it (and its remaining events) active. Use this instead of PATCH when you just want to stop a few events without re-sending the whole event_types array.

POST /apis/v1/webhooks/subscriptions/:id/unsubscribe
curl --location '{base_url}/apis/v1/webhooks/subscriptions/773aaca1-4e36-4efc-b425-1201341a1712/unsubscribe' \
  --header 'Authorization: Bearer JWT_TOKEN' \
  --header 'Content-Type: application/json' \
  --data '{ "event_types": ["case.updated", "appointment.cancelled"] }'

The response is the updated subscription.

  • Event types you list that the subscription isn't subscribed to are ignored (the call is idempotent).
  • If removing them would leave the subscription with no events, the request is rejected with 400 CANNOT_REMOVE_ALL_EVENTS — delete the subscription with DELETE /subscriptions/:id instead.

Rotate the Signature Key

POST /apis/v1/webhooks/subscriptions/:id/rotate-secret

Generates a new signature_key, returned once in the response. The previous key stops signing immediately — including in-flight retries of earlier events, which are re-signed with the new key on each attempt. Update your endpoint to verify with the new key promptly to avoid rejecting retried deliveries.

Test a Subscription

POST /apis/v1/webhooks/subscriptions/:id/test

Sends a synthetic webhook to your notification_url, signed with the subscription's current signature_key. The payload envelope includes "test": true so you can ignore test deliveries in production code paths. The response includes the dispatched payload and the HTTP result so you can debug from the API side.

Organisations

/apis/v1/webhooks/organisations exposes your organisation profile. Webhook credentials live on subscriptions, not on the organisation.

Method Path Description
GET /apis/v1/webhooks/organisations Get your organisation's profile.
PATCH /apis/v1/webhooks/organisations Update name or is_active.