Skip to main content

Webhooks

Webhooks notify your application in real-time when resources change. Every webhook payload is HMAC-SHA256 signed for security.
This page is the reference — payload, headers, settings. For how to build against it, see Webhooks in Practice: the handler shape, worked scenarios, and the four mistakes that only surface in production.

Setting Up Webhooks

  1. Go to Webhooks in the Developer Dashboard
  2. Click Create Webhook
  3. Enter your endpoint URL (must be HTTPS)
  4. Select which events to subscribe to
  5. Save — you’ll receive a webhook secret for signature verification

Endpoint requirements

The endpoint must be reachable on the public internet over https://. URLs that resolve to a non-routable address are rejected — loopback, link-local (including cloud metadata addresses), private and carrier-grade-NAT ranges, and internal names such as *.internal or *.local. Credentials embedded in the URL (https://user:pass@…) are rejected too; use the signing secret to authenticate us instead. This is enforced again at delivery time, not only when you save the webhook, so an endpoint whose DNS later points somewhere internal stops receiving deliveries rather than turning us into a proxy for it. Redirects are not followed. A 3xx response counts as a failed delivery and is retried. Register the final URL.

Event Format

Events follow the pattern {resource}.{action}:

Payload Structure

data.environment is live or test, depending on which key produced the event. Check it. Webhooks are registered per organisation, not per environment, so one endpoint receives sandbox and production events alike — and resourceId only exists in the datastore its own environment writes to, so fetching a test resource with a live key returns 404. Branch on it before you let an event drive real work: sandbox testing should not send a patient a real appointment reminder. data.tenantId identifies the tenant partition that produced the event: the sub-organization ID when the triggering API key belongs to a sub-org, otherwise your organization ID. Use it to route events per customer behind a single endpoint.

Environment Filtering

A webhook receives both environments by default, because registrations are per organisation rather than per key. When adding an endpoint you can pin it: Pin your production receiver to live. Otherwise sandbox testing drives real work — the same patient.created that fires a genuine appointment reminder — and resourceId will point at a record that exists only in the sandbox datastore, so fetching it with a live key returns 404. Existing webhooks are unpinned and keep receiving both, so nothing changes until you choose otherwise.

Sub-Organization Filtering

Webhooks are registered at the organization level, and by default an endpoint receives events from all of your traffic — organization keys and every sub-organization. When adding an endpoint you can optionally pin it to a single sub-organization: a pinned endpoint only receives events produced by that sub-org’s API keys. Either way, data.tenantId always carries the originating tenant.

Headers

Verifying Signatures

Always verify the signature before processing a webhook. Verify against the raw request body — re-serializing parsed JSON changes the bytes and the signature will not match. Prefer X-Clinik-Signature-V2: it covers the timestamp as well as the body, so you can reject a stale replay. X-Clinik-Signature covers the body alone and remains supported for existing integrations, but on its own it cannot tell a fresh delivery from a replayed one.

Event Subscription

You can subscribe to:

Available Events

All 62 resource types support .created, .updated, and .deleted events: Clinical: patient, encounter, observation, condition, allergy, assessment, care-plan, care-team, goal, risk-assessment, family-member-history Medications: medication, prescription, medication-dispense, medication-statement, medication-knowledge, immunization, immunization-evaluation, immunization-recommendation, nutrition-order Scheduling: appointment, appointment-response, schedule, slot Documentation: note, document, consent, intake, media Diagnostics: lab, specimen, imaging-study Administrative: practitioner, practitioner-role, organization, location, healthcare-service, person Workflow: task, service-request, device-request, activity-definition, plan-definition Devices: device, device-use-statement Billing and Insurance: account, charge-item, claim, claim-response, coverage, eligibility-request, eligibility-response, explanation-of-benefit, invoice, payment-notice, payment-reconciliation, enrollment-request, enrollment-response, vision-prescription Quality and Audit: measure, measure-report, audit-event

Retry Policy

Failed deliveries are retried with exponential backoff: After 5 failed attempts, the delivery is marked as failed. You can view delivery logs in the Dashboard under Analytics > Webhook Logs. Retries are per endpoint. If you have several webhooks subscribed to the same event and one of them fails, only that one is retried — the endpoints that already received the event are not sent it again. A delivery refused because the destination is not routable is not retried: the address will not become valid on its own. Fix the URL and the next event delivers normally.

Best Practices

  • Always return 200 quickly — process events asynchronously
  • Verify X-Clinik-Signature-V2 on every request, against the raw body
  • Reject deliveries whose timestamp is outside your tolerance window
  • Deduplicate on X-Clinik-Delivery-Id — delivery is at-least-once, so the same event can legitimately arrive twice
  • Use specific event subscriptions instead of * when possible