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:
| Event | Fires when |
|---|---|
appointment.created | A new appointment is booked. |
appointment.updated | An appointment changes (time, status, provider…). |
appointment.cancelled | An appointment is cancelled. |
patient.created | A new patient is added. |
patient.updated | A patient's details change. |
treatment.created | A treatment is recorded. |
treatment.updated | A recorded treatment changes. |
treatment.cancelled | A recorded treatment is removed. |
payment.recorded | A payment is recorded. |
payment.cancelled | A 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.
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:
{
"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:
| Header | Description |
|---|---|
X-Smile-Signature | sha256=<hex hmac> of the raw body, keyed by your subscription secret. |
X-Smile-Timestamp | Unix time the delivery was signed. |
X-Smile-Event-Id | The event id (same as id in the body). |
X-Smile-Event-Type | The 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.
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-Idand 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
2xxquickly (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.