agents/skills/calcom-api/references/webhooks.md
Detailed documentation for webhook management endpoints in the Cal.com API v2.
| Method | Endpoint | Description |
|---|---|---|
| GET | /v2/webhooks | List webhooks |
| POST | /v2/webhooks | Create a webhook |
| GET | /v2/webhooks/{webhookId} | Get a webhook |
| PATCH | /v2/webhooks/{webhookId} | Update a webhook |
| DELETE | /v2/webhooks/{webhookId} | Delete a webhook |
GET /v2/webhooks
{
"status": "success",
"data": [
{
"id": "webhook-id-123",
"subscriberUrl": "https://your-app.com/webhook",
"triggers": ["BOOKING_CREATED", "BOOKING_CANCELLED"],
"active": true,
"payloadTemplate": null,
"secret": "whsec_..."
}
]
}
POST /v2/webhooks
{
"subscriberUrl": "https://your-app.com/webhook",
"triggers": ["BOOKING_CREATED", "BOOKING_CANCELLED", "BOOKING_RESCHEDULED"],
"active": true,
"payloadTemplate": null,
"secret": "your-webhook-secret"
}
| Field | Type | Required | Description |
|---|---|---|---|
| subscriberUrl | string | Yes | URL to receive webhook payloads |
| triggers | array | Yes | Events that trigger the webhook |
| active | boolean | No | Enable/disable webhook (default: true) |
| payloadTemplate | string | No | Custom payload template |
| secret | string | No | Secret for signature verification |
| Trigger | Description |
|---|---|
| BOOKING_CREATED | New booking created |
| BOOKING_CANCELLED | Booking cancelled |
| BOOKING_RESCHEDULED | Booking rescheduled |
| BOOKING_CONFIRMED | Pending booking confirmed |
| BOOKING_REJECTED | Booking rejected |
| BOOKING_REQUESTED | Booking request received (requires confirmation) |
| BOOKING_PAYMENT_INITIATED | Payment started |
| BOOKING_NO_SHOW_UPDATED | Attendee marked as no-show |
| MEETING_STARTED | Video meeting started |
| MEETING_ENDED | Video meeting ended |
| RECORDING_READY | Meeting recording available |
| INSTANT_MEETING | Instant meeting created |
| RECORDING_TRANSCRIPTION_GENERATED | Transcription ready |
| FORM_SUBMITTED | Routing form submitted |
{
"triggerEvent": "BOOKING_CREATED",
"createdAt": "2024-01-15T10:00:00.000Z",
"payload": {
"type": "30min",
"title": "30 Minute Meeting",
"description": "Meeting description",
"startTime": "2024-01-15T10:00:00.000Z",
"endTime": "2024-01-15T10:30:00.000Z",
"organizer": {
"id": 123,
"name": "Jane Smith",
"email": "[email protected]",
"timeZone": "America/New_York"
},
"attendees": [
{
"name": "John Doe",
"email": "[email protected]",
"timeZone": "America/New_York"
}
],
"location": "https://cal.com/video/abc123",
"destinationCalendar": {
"integration": "google_calendar",
"externalId": "calendar-id"
},
"uid": "booking-uid-123",
"metadata": {},
"responses": {
"name": "John Doe",
"email": "[email protected]"
}
}
}
{
"triggerEvent": "BOOKING_CANCELLED",
"createdAt": "2024-01-15T12:00:00.000Z",
"payload": {
"uid": "booking-uid-123",
"title": "30 Minute Meeting",
"startTime": "2024-01-15T10:00:00.000Z",
"endTime": "2024-01-15T10:30:00.000Z",
"cancellationReason": "Schedule conflict",
"organizer": {...},
"attendees": [...]
}
}
{
"triggerEvent": "BOOKING_RESCHEDULED",
"createdAt": "2024-01-15T11:00:00.000Z",
"payload": {
"uid": "new-booking-uid",
"rescheduleUid": "original-booking-uid",
"title": "30 Minute Meeting",
"startTime": "2024-01-16T14:00:00.000Z",
"endTime": "2024-01-16T14:30:00.000Z",
"reschedulingReason": "Conflict with another meeting",
"organizer": {...},
"attendees": [...]
}
}
Webhooks include a signature header for verification:
X-Cal-Signature-256: sha256=<signature>
const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return `sha256=${expectedSignature}` === signature;
}
// In your webhook handler
app.post('/webhook', (req, res) => {
const signature = req.headers['x-cal-signature-256'];
const payload = JSON.stringify(req.body);
if (!verifyWebhookSignature(payload, signature, process.env.WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
// Process webhook
const { triggerEvent, payload: data } = req.body;
// ...
res.status(200).send('OK');
});
GET /v2/webhooks/{webhookId}
PATCH /v2/webhooks/{webhookId}
{
"active": false,
"triggers": ["BOOKING_CREATED"]
}
DELETE /v2/webhooks/{webhookId}
Create webhooks specific to an event type:
GET /v2/event-types/{eventTypeId}/webhooks
POST /v2/event-types/{eventTypeId}/webhooks
{
"subscriberUrl": "https://your-app.com/webhook",
"triggers": ["BOOKING_CREATED"],
"active": true
}
For organization-wide webhooks:
GET /v2/organizations/{orgId}/webhooks
POST /v2/organizations/{orgId}/webhooks
For platform integrations:
GET /v2/oauth-clients/{clientId}/webhooks
POST /v2/oauth-clients/{clientId}/webhooks
Customize webhook payloads using templates:
{
"subscriberUrl": "https://your-app.com/webhook",
"triggers": ["BOOKING_CREATED"],
"payloadTemplate": "{\"event\": \"{{triggerEvent}}\", \"booking_id\": \"{{payload.uid}}\", \"attendee\": \"{{payload.attendees[0].email}}\"}"
}
{{triggerEvent}} - Event type{{payload.uid}} - Booking UID{{payload.title}} - Event title{{payload.startTime}} - Start time{{payload.endTime}} - End time{{payload.organizer.name}} - Organizer name{{payload.organizer.email}} - Organizer email{{payload.attendees[0].name}} - First attendee name{{payload.attendees[0].email}} - First attendee emailFailed webhook deliveries are retried with exponential backoff:
After 5 failed attempts, the webhook is marked as failed.