Webhooks
Inbound endpoints that messaging and email providers call to deliver messages and delivery statuses.
These endpoints receive events from providers into SmileLine — they power the Inbox. You don't call them yourself: connecting a channel in Settings → Channels gives you the exact URL to paste into the provider's dashboard (or configures it for you).
| Endpoint | Provider | Purpose |
|---|---|---|
GET /webhooks/whatsapp | Meta | Subscription handshake (echoes hub.challenge). |
POST /webhooks/whatsapp | Meta | Inbound WhatsApp messages and delivery statuses. |
POST /webhooks/twilio/{token} | Twilio | Inbound SMS. The token routes to your channel connection. |
POST /webhooks/twilio/{token}/status | Twilio | SMS delivery status callbacks. |
POST /webhooks/resend | Resend | Email delivery statuses. |
Security
Every webhook verifies the provider's signature against the raw request body before anything is parsed — Meta's app-secret signature, Twilio's request signature, Resend's svix signature. Requests that fail verification are rejected.
Payloads that arrive for an unknown account, or that duplicate an earlier delivery, are acknowledged with a 2xx and dropped — providers retry on non-2xx, and retry loops help no one.
Looking for outbound webhooks?
SmileLine does not send webhooks to your systems yet. If you need to react to CRM changes, poll the relevant list endpoints for now.
Lead capture is not a webhook
POST /capture/{token} looks similar but is a different thing: a public intake endpoint your website forms submit to. It's covered in the Lead capture guide and documented under Capture in the sidebar.
Rate limits
Token-bucket limits protect the public capture endpoint and AI features. Everything else is unmetered today.
List patients GET
Paginated list. q is a typo-tolerant (trigram) search over name, email and phone. `filters` is URL-encoded JSON matching the PatientFilters schema; keys are AND-ed. sort=relevance ranks by similarity to q. embed=tags attaches each row's tags. Archived patients are excluded unless includeArchived=true or a status filter asks for archived.