Webhooks
Clearslot sends a signed HTTPS request to your endpoint when something changes in your business: a booking is made, moved or cancelled, a customer record changes, or a booking link is edited. Every Pro business and every business with a complimentary grant can use them.
Use webhooks to react within seconds. Use GET /api/v1/events to catch up after downtime or to check that you saw everything.
Events
| Type | When |
|---|---|
booking.created | A booking exists. Requests that await approval arrive with status pending. Reservations that still wait for email verification, online payment or a waitlist answer are never sent. |
booking.confirmed | A booking request was approved. |
booking.rescheduled | A booking moved. previous holds the old start and end. |
booking.cancelled | A booking was cancelled by the host, the guest or a connected calendar, or a request was declined. |
booking.no_show_marked, booking.no_show_cleared | Attendance changed. |
booking.payment_succeeded, booking.payment_refunded | An online payment settled or was refunded. |
customer.created, customer.updated, customer.erased | The customer register changed. |
booking_link.created, booking_link.updated, booking_link.deleted | A booking link's own settings changed. |
routing_form.submitted | A routing form was submitted. |
webhook.ping | Verification and test requests. Every endpoint receives it. |
Subscribe to explicit types. New types never reach an endpoint that did not ask for them. Subscribing to a family needs its read scope: bookings:read, customers:read, or booking_links:read for booking links and routing forms.
The request
POST /your/path HTTP/1.1
Content-Type: application/json
webhook-id: 7d0f3c1e-5b8a-4c0e-9a51-2f6c1d3e8b90
webhook-timestamp: 1793180000
webhook-signature: v1,K5oZfzN95Z9UVu1EsfQmfVNQhnkZ2pj9o9NDN/H/pI4=
{
"id": "7d0f3c1e-5b8a-4c0e-9a51-2f6c1d3e8b90",
"object": "event",
"type": "booking.rescheduled",
"apiVersion": 1,
"occurredAt": "2026-11-02T09:14:03.120Z",
"subject": { "object": "booking", "id": "…" },
"details": { "rescheduledBy": "guest", "trigger": "move" },
"previous": { "startTime": "…", "endTime": "…" },
"data": { "object": "booking", "id": "…", "status": "confirmed", "startTime": "…" },
"timestamp": "2026-11-02T09:14:05.480Z"
}
subjectnames the resource the event is about.detailsholds the event's own facts, such as who cancelled (cancelledBy,trigger), who moved a booking, the amount paid or refunded (amountMinor,currency), a new booking'sstatusor a submission'sroutingFormId. It is{}when an event has none.datais the resource asGETreturns it when the request is sent, not when the event happened.type,occurredAt,detailsandpreviousdescribe the change. A resource that was deleted or whose guest was erased arrives as{ "object": "booking", "id": "…", "deleted": true }or with the erased placeholders.webhook-idis the event ID. It is the same on every retry, so use it to ignore duplicates.- Ordering is not guaranteed. Compare
occurredAt, or read the resource. - Bodies are at most 64 KB. Intake answers are left out first when a resource is larger.
GET /api/v1/events?expand=datareturns events in exactly this shape, without the per-attempttimestamp.
The event feed
GET /api/v1/events lists the business's events from the last 30 days, newest first, with cursor paging. Without expand, each event has everything above except data. Filter with type, one type or several separated by commas. expand=data renders each subject as a webhook delivery does; limit is then at most 25. A key sees an event only if it holds the read scope of the event's family, so a key with only bookings:read lists booking events alone.
Verify the signature
Requests follow the Standard Webhooks specification. The signature is HMAC-SHA256 over webhook-id, webhook-timestamp and the raw body, joined with dots, using your endpoint's whsec_… secret. Verify the raw body before parsing it, and reject timestamps more than 5 minutes old.
Node.js, with the standardwebhooks package:
import { Webhook } from 'standardwebhooks';
const webhook = new Webhook(process.env.CLEARSLOT_WEBHOOK_SECRET);
// rawBody: the exact bytes received, as a string.
const event = webhook.verify(rawBody, {
'webhook-id': request.headers['webhook-id'],
'webhook-timestamp': request.headers['webhook-timestamp'],
'webhook-signature': request.headers['webhook-signature']
});
Python, with the standardwebhooks package:
from standardwebhooks.webhooks import Webhook
event = Webhook(secret).verify(raw_body, headers)
Without a library:
import { createHmac, timingSafeEqual } from 'node:crypto';
const key = Buffer.from(secret.slice('whsec_'.length), 'base64');
const expected = createHmac('sha256', key).update(`${id}.${timestamp}.${rawBody}`).digest('base64');
const valid = signatureHeader.split(' ').some(part => {
const [version, value] = part.split(',');
return version === 'v1' && value.length === expected.length && timingSafeEqual(Buffer.from(value), Buffer.from(expected));
});
Rotating the secret keeps the old one valid for 24 hours. During that time webhook-signature carries two signatures separated by a space, so a receiver that still has the old secret keeps working until you deploy the new one.
Answer quickly
Return any 2xx status within 10 seconds. Do the work afterwards. Clearslot reads at most 64 KB of your response and ignores its body.
Verification, retries and failures
- A new endpoint is verified first. Clearslot sends a signed
webhook.pingright away and again after about 10 seconds, 1 minute and 5 minutes. The endpoint becomes active when it answers one of them with a2xx. Until then it receives no events. Sending a test re-sends the ping. - Failed deliveries are retried about nine times over three days: after roughly 1 minute, 5 minutes, 30 minutes, 2, 6, 12, 24 and 24 hours, each with a little jitter.
Retry-Afteron a429or503can delay the next attempt up to the following scheduled one. - Redirects are not followed. A
3xxcounts as a failure. 410 Goneturns a dashboard endpoint off and emails the business owners. An endpoint created through the API is deleted (REST Hooks).- Five days without a successful delivery turn an endpoint off. Owners are warned on day 3. Turning it on again lets you retry the failed deliveries of the last 30 days.
- Plan lapse. While a business has neither Pro nor a complimentary grant, its endpoints pause and receive nothing. Events stay in
GET /api/v1/eventsfor 30 days. - Daily cap. A business can use 20,000 delivery attempts a day. Attempts beyond the cap wait until the next UTC day; they are not dropped.
The delivery log in Settings → Developers → Webhooks shows every attempt from the last 30 days: status, attempts, response status, latency and an error class (timeout, dns, tls, refused, blocked_address, http_status, too_large). Request and response bodies are never stored.
Where an endpoint can live
https://on port 443, at most 2,048 characters- a public host name: no IP addresses, no
localhost,.localor.internalnames, no user name or password in the URL, and no Clearslot hosts - the name must resolve to public addresses on every attempt. Requests to private, loopback, link-local or similar addresses are refused as
blocked_address.
Filters
An endpoint can be limited to some team members, some booking links, or both. Filters apply to booking and booking-link events; endpoints with filters can't subscribe to customer or routing-form events. A staff member's own integration can receive only their bookings this way.
Authentication header
Some receivers can check a fixed header but not a signature. Give the endpoint one custom header, for example X-Clearslot-Token, and it is sent with every request. The value is encrypted, never shown again after saving and never logged. Host, Content-*, webhook-* and connection headers are reserved. Prefer the signature when your receiver can check it.
Endpoints through the API (REST Hooks)
Automation platforms create one endpoint per workflow when it is turned on, and delete it when it is turned off:
POST /api/v1/webhook-endpoints
{ "url": "https://hooks.example.com/abc", "eventTypes": ["booking.created"] }
The response is { "endpoint": { … }, "secret": "whsec_…" }. The secret is returned once; store it to verify signatures. An Idempotency-Key is ignored on this request, because the response carries the secret.
| Request | Does |
|---|---|
GET /api/v1/webhook-endpoints, GET /api/v1/webhook-endpoints/{id} | List or show endpoints (webhooks:read) |
PATCH /api/v1/webhook-endpoints/{id} | Change url, description, eventTypes, filters or authHeader (null removes it), or turn it off and on with enabled |
DELETE /api/v1/webhook-endpoints/{id} | Unsubscribe |
POST /api/v1/webhook-endpoints/{id}/test | Send webhook.ping now; a pending endpoint that answers becomes active |
POST /api/v1/webhook-endpoints/{id}/rotate-secret | Return a new secret once; the old one stays valid for 24 hours |
GET /api/v1/webhook-endpoints/{id}/deliveries | The delivery log, newest first |
POST /api/v1/webhook-deliveries/{id}/retry | Send a finished event delivery again with a fresh body |
An API-created endpoint that answers 410 is deleted, and one that never verifies is deleted after 24 hours, so a platform that misses an unsubscribe does not leave endpoints behind. A changed URL is verified again before events resume. A business can have 100 endpoints. Creating them needs webhooks:write plus the read scope of each event family. An API key or OAuth app sees and manages only the endpoints it created. Revoking a key removes its endpoints, and once a key expires its endpoints receive nothing; a rolled key keeps them. Signing secrets are shown again, or replaced, in the dashboard only to owners and admins who confirm their password or two-factor code.
Hand-built automations
Until the Zapier, Make and n8n apps are published, or if you prefer building the flow yourself, register the platform's catch URL as an endpoint in Settings → Developers → Webhooks, and call the API with an API key in later steps (Authorization: Bearer cs_live_…).
n8n
- Add a Webhook node with HTTP method
POST. Copy its production URL. - Under Authentication, choose Header Auth and create a credential
with a header name such as X-Clearslot-Token and a long random value.
- In Clearslot, add an endpoint with that URL, choose the events, and enter
the same header name and value as the authentication header.
- Activate the workflow before saving the endpoint, so the verification ping
gets a 2xx. The ping has type webhook.ping; add an If node that stops the workflow for it.
- To check signatures instead, set the Webhook node's Raw Body option and
verify in a Code node with the snippet above.
- Call the API with an HTTP Request node and a Header Auth credential
(Authorization, Bearer cs_live_…).
Make
- Start a scenario with Webhooks → Custom webhook and copy its address.
- Add the address as an endpoint in Clearslot. Make answers the ping with
200 while the scenario listens, which verifies the endpoint.
- Filter out
webhook.pingwith a filter ontypeafter the trigger. - Make's custom webhook can't check signatures; use the authentication header
and a filter on its value. Under the webhook's advanced settings, enable Get request headers to read it.
- Call the API with HTTP → Make a request, adding the
Authorization
header.
Zapier (Webhooks by Zapier)
- Create a Zap with Webhooks by Zapier → Catch Hook (use **Catch Raw
Hook** if you want to verify signatures in a Code step) and copy the URL.
- Add it as an endpoint in Clearslot, then use Test trigger; Zapier keeps
the ping as sample data. Add a Filter step that continues only when type is not webhook.ping.
- Catch Hook can't check signatures. Use the authentication header and a
Filter on the header value, or use Catch Raw Hook with a Code by Zapier step that verifies the signature.
- Call the API with Webhooks by Zapier → Custom Request and the
Authorization header.
For sample data while building, GET /api/v1/events?expand=data&type=booking.created&limit=1 returns a recent event in the same shape as a webhook body.