SmileCloudDocs

Webhooks

Webhooks push clinic events to your server as they happen, so you don't have to poll. When something changes - an appointment is booked, a patient is updated, a payment is recorded - the API sends a signed POST to your endpoint with the event payload.

#Event types

Subscribe to any combination of these events:

EventFires when
appointment.createdA new appointment is booked.
appointment.updatedAn appointment changes (time, status, provider…).
appointment.cancelledAn appointment is cancelled.
patient.createdA new patient is added.
patient.updatedA patient's details change.
treatment.createdA treatment is recorded.
treatment.updatedA recorded treatment changes.
treatment.cancelledA recorded treatment is removed.
payment.recordedA payment is recorded.
payment.cancelledA payment is cancelled/refunded.

#Subscribe

Create a subscription with the URL to deliver to and the events you care about. The response includes a signing secret - save it, it's shown only once.

bash
curl -X POST https://api.smile-app.co.il/v1/webhooks \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/hooks/smile",
    "event_types": ["appointment.created", "appointment.updated", "appointment.cancelled"]
  }'

See the Webhooks API reference for the full set of management endpoints.

#Payload

Each delivery is a POST with a JSON body shaped like this:

json
{
  "id": "evt_9f2a7c",
  "type": "appointment.created",
  "created_at": "2026-07-01T09:00:05+03:00",
  "data": {
    "id": "55021",
    "patient": { "id": "8842", "first_name": "Dana", "last_name": "Levi" },
    "branch": { "id": "1", "name": "Downtown Branch" },
    "start": "2026-07-01T09:00:00+03:00",
    "end": "2026-07-01T09:30:00+03:00"
  }
}

data mirrors the REST resource

The data object is byte-identical to the matching REST resource (an appointment, patient, treatment, or payment). Whatever you'd get from a GET, you get in the event - no second lookup needed.

#Headers

Every delivery carries these headers:

HeaderDescription
X-Smile-Signaturesha256=<hex hmac> of the raw body, keyed by your subscription secret.
X-Smile-TimestampUnix time the delivery was signed.
X-Smile-Event-IdThe event id (same as id in the body).
X-Smile-Event-TypeThe event type.

#Verify the signature

Always verify the signature before trusting a payload. Compute the HMAC-SHA256 of the raw request body using your subscription secret, and compare it to X-Smile-Signature with a constant-time comparison.

Node.js
import crypto from "node:crypto";

function verify(rawBody, signatureHeader, secret) {
  const expected =
    "sha256=" + crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
  const a = Buffer.from(signatureHeader);
  const b = Buffer.from(expected);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

Verify on the raw body

Compute the HMAC over the exact bytes you received, before any JSON parsing or re-serialization. Re-encoding the body will change the bytes and break the signature.

#Delivery, retries, and ordering

  • At-least-once. A delivery may arrive more than once. Deduplicate on X-Smile-Event-Id and make your handler idempotent.
  • Retries. Failed deliveries (non-2xx or timeout) are retried with exponential backoff for up to ~24 hours.
  • Auto-disable. A subscription that keeps failing is automatically disabled; re-enable it by fixing your endpoint and creating a new subscription.
  • Respond fast. Return 2xx quickly (ideally after just enqueuing the event). Do heavy work asynchronously so you don't time out.

#Inspect and replay

Use the deliveries log to see attempts and outcomes, and retry a specific failed delivery once your endpoint is healthy again.