Webhook Subscriptions
Manage webhook endpoints that receive scheduling-lifecycle events from the Platform API. The underlying delivery infrastructure is documented in full on the Webhook Basics page — HMAC-SHA256 signatures, retries with exponential backoff, and delivery logs.
The Platform API surfaces a programmatic CRUD interface so AI agents and integrations can manage their own subscriptions without going through the developer dashboard.
| Operation | Scope required |
|---|---|
| List, get, list deliveries | webhooks:read |
| Create, delete | webhooks:write |
Endpoints on this page
| Method | Endpoint | Description |
|---|---|---|
GET | /api/v1alpha1/webhook-subscriptions | List subscriptions owned by the calling org. Details ↓ |
POST | /api/v1alpha1/webhook-subscriptions | Create a subscription. Signing secret is revealed once in the response. Details ↓ |
GET | /api/v1alpha1/webhook-subscriptions/{subscriptionId} | Read one subscription's current state (no signing secret). Details ↓ |
DELETE | /api/v1alpha1/webhook-subscriptions/{subscriptionId} | Soft-delete a subscription. Idempotent — second call also returns 204. Details ↓ |
GET | /api/v1alpha1/webhook-subscriptions/{subscriptionId}/deliveries | Read delivery-attempt history for a subscription. Details ↓ |
Available event types
Every event is delivered inside a canonical Eventmodel envelope. The event.data shape depends on the event type — see the per-family pages linked in the last column for the full field reference.
Scheduling (details)
event.data is a canonical Meetingmodel object.
| Event type | When it fires |
|---|---|
scheduling.smartscheduler.scheduled | A Smart Scheduler meeting is booked |
scheduling.smartscheduler.rescheduled | A Smart Scheduler meeting is rescheduled |
scheduling.smartscheduler.canceled | A Smart Scheduler meeting is canceled |
scheduling.personalscheduling.scheduled | A personal-scheduling-page meeting is booked |
scheduling.personalscheduling.rescheduled | A personal-scheduling-page meeting is rescheduled |
scheduling.personalscheduling.canceled | A personal-scheduling-page meeting is canceled |
scheduling.teamscheduling.scheduled | A team-scheduling-page meeting is booked |
scheduling.teamscheduling.rescheduled | A team-scheduling-page meeting is rescheduled |
scheduling.teamscheduling.canceled | A team-scheduling-page meeting is canceled |
Configuration (details)
event.data is a change-history record with previous, current, and modifiedBy fields.
| Event type | When it fires |
|---|---|
organization.setting.changed | An organization-level setting is modified |
organization.profile.changed | The company profile (name, web address, logo) is modified |
organization.service_account.changed | A service-account configuration is added, modified, or removed |
organization.domains.changed | The organization's domain list is modified |
Developer (details)
Delivered automatically as part of the subscription-validation flow — not opt-in via the events array. Every new subscription receives one regardless of what business events it subscribed to.
| Event type | When it fires |
|---|---|
developer.webhook.test | A new subscription enters the validating state |
List webhook subscriptions
GET /api/v1alpha1/webhook-subscriptions
Returns subscriptions owned by the calling org.
Query parameters
| Parameter | Type | Description |
|---|---|---|
cursor | string | Opaque pagination cursor. |
limit | number | Page size. Default 25, clamped to 1–100. |
sort | string | createdAt or updatedAt, prefix - for descending. Default -updatedAt. |
Response
status is one of:
active— accepting and delivering eventsdisabled— administratively paused (typically after repeated delivery failures put the subscription in probation, then failed to recover)validating— set on initial create until the system successfully pings the endpoint
lastDeliveryAt is the timestamp of the most recent delivery attempt (successful or failed) and is the primary "is my webhook healthy" signal.
signingSecret is never returned by this endpoint — it's revealed exactly once, on create.
curl -s "https://altus.cirrusinsight.com/api/v1alpha1/webhook-subscriptions?limit=25" \
-H "X-Cirrus-Api-Key: $TOKEN"import requests
response = requests.get(
"https://altus.cirrusinsight.com/api/v1alpha1/webhook-subscriptions",
headers={"X-Cirrus-Api-Key": token},
params={"limit": 25},
)
response.raise_for_status()
page = response.json()using System.Net.Http;
using System.Net.Http.Headers;
using System.Web;
var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-Cirrus-Api-Key", token);
var query = HttpUtility.ParseQueryString(string.Empty);
query["limit"] = "25";
var uri = new UriBuilder("https://altus.cirrusinsight.com/api/v1alpha1/webhook-subscriptions")
{
Query = query.ToString()
}.Uri;
var response = await client.GetAsync(uri);
response.EnsureSuccessStatusCode();
var page = await response.Content.ReadAsStringAsync();{
"items": [
{
"id": "sub_...",
"endpointUrl": "https://example.com/cirrus-webhook",
"events": [
"scheduling.smartscheduler.scheduled",
"scheduling.smartscheduler.rescheduled",
"scheduling.smartscheduler.canceled"
],
"status": "active",
"description": "Production booking sync",
"createdAt": "2026-06-01T12:00:00Z",
"lastDeliveryAt": "2026-07-28T09:12:00Z"
}
],
"nextCursor": null
}Create a webhook subscription
POST /api/v1alpha1/webhook-subscriptions
Request body
| Field | Type | Description |
|---|---|---|
endpointUrl | string | The HTTPS endpoint that will receive event deliveries. Must be https://; localhost and RFC1918 addresses are rejected server-side. |
events | list | One or more allowed event types (see above). Must be a non-empty subset of the v1alpha1 catalog. |
description | string | (optional, max 200 chars) Free-text label to help you identify the subscription in list views. |
The signing secret is generated by the server — you do not provide it. See the response below.
Response
201 Created:
The signingSecret is returned exactly once, on this response. It is never included in subsequent GET responses. Store it in your secrets manager immediately; a lost secret cannot be recovered — delete the subscription and create a new one.
New subscriptions start in status: "validating". The system pings your endpoint before treating it as active; a subscription that never validates transitions to disabled and stops receiving events.
Errors
422 webhook-endpoint-invalid—endpointUrlis not HTTPS, or targets a private / loopback address422 webhook-events-invalid— empty event list, or contains an event not in the v1alpha1 catalog422 webhook-limit-reached— your org already has the maximum number of subscriptions (10 in v1)
Verifying deliveries
Deliveries include:
X-Cirrus-Signature: sha256=<hex>— HMAC-SHA256 of the raw request body signed with your subscription'ssigningSecretX-Cirrus-Event-Type— e.g.,scheduling.smartscheduler.scheduledX-Cirrus-Correlation-Id— shared across all retry attempts for one event
Always verify the signature before processing the payload. See Webhook Security for the verification recipe.
curl -s -X POST "https://altus.cirrusinsight.com/api/v1alpha1/webhook-subscriptions" \
-H "X-Cirrus-Api-Key: $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"endpointUrl": "https://example.com/cirrus-webhook",
"events": [
"scheduling.smartscheduler.scheduled",
"scheduling.smartscheduler.rescheduled",
"scheduling.smartscheduler.canceled"
],
"description": "Production booking sync"
}'import requests
response = requests.post(
"https://altus.cirrusinsight.com/api/v1alpha1/webhook-subscriptions",
headers={"X-Cirrus-Api-Key": token},
json={
"endpointUrl": "https://example.com/cirrus-webhook",
"events": [
"scheduling.smartscheduler.scheduled",
"scheduling.smartscheduler.rescheduled",
"scheduling.smartscheduler.canceled",
],
"description": "Production booking sync",
},
)
response.raise_for_status()
subscription = response.json()using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-Cirrus-Api-Key", token);
var body = new
{
endpointUrl = "https://example.com/cirrus-webhook",
events = new[]
{
"scheduling.smartscheduler.scheduled",
"scheduling.smartscheduler.rescheduled",
"scheduling.smartscheduler.canceled",
},
description = "Production booking sync",
};
var request = new HttpRequestMessage(
HttpMethod.Post,
"https://altus.cirrusinsight.com/api/v1alpha1/webhook-subscriptions")
{
Content = new StringContent(
JsonSerializer.Serialize(body),
Encoding.UTF8,
"application/json"),
};
var response = await client.SendAsync(request);
response.EnsureSuccessStatusCode();
var subscription = await response.Content.ReadAsStringAsync();{
"id": "sub_...",
"endpointUrl": "https://example.com/cirrus-webhook",
"events": [
"scheduling.smartscheduler.scheduled",
"scheduling.smartscheduler.rescheduled",
"scheduling.smartscheduler.canceled"
],
"status": "validating",
"description": "Production booking sync",
"createdAt": "2026-06-10T14:22:01Z",
"signingSecret": "whsec_...",
"signingSecretNote": "Save this now — it will not be shown again."
}Get a webhook subscription
GET /api/v1alpha1/webhook-subscriptions/{subscriptionId}
Returns one subscription's current state.
Path parameters
| Parameter | Type | Description |
|---|---|---|
subscriptionId | string | Subscription ID, prefixed sub_. |
Response
200 OK with the same shape as one entry in the list response (no signingSecret).
Errors
404 webhook-subscription-not-found— the subscription does not exist or does not belong to this org
curl -s "https://altus.cirrusinsight.com/api/v1alpha1/webhook-subscriptions/sub_01H8YKQ2N9WEBHK" \
-H "X-Cirrus-Api-Key: $TOKEN"import requests
subscription_id = "sub_01H8YKQ2N9WEBHK"
response = requests.get(
f"https://altus.cirrusinsight.com/api/v1alpha1/webhook-subscriptions/{subscription_id}",
headers={"X-Cirrus-Api-Key": token},
)
response.raise_for_status()
subscription = response.json()using System.Net.Http;
using System.Net.Http.Headers;
var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-Cirrus-Api-Key", token);
var subscriptionId = "sub_01H8YKQ2N9WEBHK";
var response = await client.GetAsync(
$"https://altus.cirrusinsight.com/api/v1alpha1/webhook-subscriptions/{subscriptionId}");
response.EnsureSuccessStatusCode();
var subscription = await response.Content.ReadAsStringAsync();Delete a webhook subscription
DELETE /api/v1alpha1/webhook-subscriptions/{subscriptionId}
Path parameters
| Parameter | Type | Description |
|---|---|---|
subscriptionId | string | Subscription ID, prefixed sub_. |
Response
204 No Content on success. The subscription is soft-deleted — the audit trail is preserved, but the row is no longer visible to any endpoint and subsequent events are not delivered to it.
Deleting an already-deleted subscription also returns 204. DELETE states a desired end condition; a retry after a client timeout succeeds silently.
There is no PATCH endpoint in v1 — to change a subscription's URL or event set, delete it and create a new one.
Errors
404 webhook-subscription-not-found— the subscription is not visible to this org (never existed, or belongs to another org)
curl -s -X DELETE "https://altus.cirrusinsight.com/api/v1alpha1/webhook-subscriptions/sub_01H8YKQ2N9WEBHK" \
-H "X-Cirrus-Api-Key: $TOKEN"import requests
subscription_id = "sub_01H8YKQ2N9WEBHK"
response = requests.delete(
f"https://altus.cirrusinsight.com/api/v1alpha1/webhook-subscriptions/{subscription_id}",
headers={"X-Cirrus-Api-Key": token},
)
response.raise_for_status()using System.Net.Http;
using System.Net.Http.Headers;
var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-Cirrus-Api-Key", token);
var subscriptionId = "sub_01H8YKQ2N9WEBHK";
var response = await client.DeleteAsync(
$"https://altus.cirrusinsight.com/api/v1alpha1/webhook-subscriptions/{subscriptionId}");
response.EnsureSuccessStatusCode();List delivery attempts
GET /api/v1alpha1/webhook-subscriptions/{subscriptionId}/deliveries
Returns the history of delivery attempts for a subscription, ordered most-recent first. Useful for diagnosing missed events or failed deliveries.
Query parameters
| Parameter | Type | Description |
|---|---|---|
cursor | string | Pagination cursor. |
limit | number | Page size. Default 25, clamped to 1–100. |
sort | string | attemptedAt only, prefix - for descending. Default -attemptedAt. |
status | string | (optional, repeatable) succeeded | failed | pending. |
eventType | string | (optional) Filter to one event type. |
since | string | (optional) ISO 8601 UTC timestamp. Deliveries after this time only. Max lookback 90 days. |
correlationId | string | (optional) Match a specific event's retry chain — every delivery for one event shares one correlation id. |
Response
Delivery items omit request and response bodies to keep list responses small.
Failed deliveries (status: "failed") are automatically retried with exponential backoff for up to 24 hours; see Webhook Lifecycle for the retry policy.
Errors
404 webhook-subscription-not-found— the subscription is not visible to this org
curl -s "https://altus.cirrusinsight.com/api/v1alpha1/webhook-subscriptions/sub_01H8YKQ2N9WEBHK/deliveries?limit=25&status=failed" \
-H "X-Cirrus-Api-Key: $TOKEN"import requests
subscription_id = "sub_01H8YKQ2N9WEBHK"
response = requests.get(
f"https://altus.cirrusinsight.com/api/v1alpha1/webhook-subscriptions/{subscription_id}/deliveries",
headers={"X-Cirrus-Api-Key": token},
params={"limit": 25, "status": "failed"},
)
response.raise_for_status()
page = response.json()using System.Net.Http;
using System.Net.Http.Headers;
using System.Web;
var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-Cirrus-Api-Key", token);
var subscriptionId = "sub_01H8YKQ2N9WEBHK";
var query = HttpUtility.ParseQueryString(string.Empty);
query["limit"] = "25";
query["status"] = "failed";
var uri = new UriBuilder(
$"https://altus.cirrusinsight.com/api/v1alpha1/webhook-subscriptions/{subscriptionId}/deliveries")
{
Query = query.ToString()
}.Uri;
var response = await client.GetAsync(uri);
response.EnsureSuccessStatusCode();
var page = await response.Content.ReadAsStringAsync();{
"items": [
{
"id": "wd_...",
"eventType": "scheduling.smartscheduler.scheduled",
"eventId": "mtg_...",
"correlationId": "corr_...",
"attemptNumber": 1,
"status": "succeeded",
"responseCode": 200,
"responseTimeMs": 87,
"attemptedAt": "2026-07-28T09:12:00Z",
"nextRetryAt": null
},
{
"id": "wd_...",
"eventType": "scheduling.smartscheduler.canceled",
"eventId": "mtg_...",
"correlationId": "corr_...",
"attemptNumber": 3,
"status": "failed",
"responseCode": null,
"responseTimeMs": null,
"attemptedAt": "2026-07-28T08:33:01Z",
"nextRetryAt": "2026-07-28T08:38:01Z"
}
],
"nextCursor": "eyJmaWVsZCI6ImF0dGVtcHRlZEF0Iiw..."
}