Skip to main content

Webhooks

Webhooks notify your application in real-time when resources change. Every webhook payload is HMAC-SHA256 signed for security.

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

Event Format

Events follow the pattern {resource}.{action}:
patient.created
encounter.updated
prescription.deleted

Payload Structure

{
  "id": "evt_lx8f3a7x2_k9m2n4",
  "event": "patient.created",
  "timestamp": "2025-01-15T09:15:00.000Z",
  "data": {
    "resourceType": "patient",
    "resourceId": "pt_abc123",
    "action": "created",
    "tenantId": "org_def456"
  }
}

Headers

HeaderDescription
X-Clinik-SignatureHMAC-SHA256 hex signature of the payload
X-Clinik-TimestampUnix timestamp (seconds) when the webhook was sent
X-Clinik-EventEvent type (e.g. patient.created)
X-Clinik-Delivery-IdUnique delivery ID for deduplication

Verifying Signatures

Always verify the signature before processing a webhook:
import crypto from 'crypto';

function verifyWebhook(payload: string, signature: string, secret: string): boolean {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');
  return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}

// In your webhook handler
app.post('/webhooks/clinikapi', (req, res) => {
  const signature = req.headers['x-clinik-signature'];
  const isValid = verifyWebhook(req.body, signature, process.env.CLINIK_WEBHOOK_SECRET);

  if (!isValid) {
    return res.status(401).send('Invalid signature');
  }

  const event = JSON.parse(req.body);
  switch (event.event) {
    case 'patient.created':
      // Handle new patient
      break;
    case 'appointment.updated':
      // Handle appointment change
      break;
  }

  res.status(200).send('OK');
});

Event Subscription

You can subscribe to:
PatternDescription
*All events
patient.*All patient events (created, updated, deleted)
patient.createdOnly patient creation

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:
AttemptDelay
130 seconds
22 minutes
38 minutes
432 minutes
52 hours
After 5 failed attempts, the delivery is marked as failed. You can view delivery logs in the Dashboard under Analytics > Webhook Logs.

Best Practices

  • Always return 200 quickly — process events asynchronously
  • Use the X-Clinik-Delivery-Id header for idempotency
  • Verify signatures on every request
  • Use specific event subscriptions instead of * when possible