> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/calcom/cal.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

> Receive real-time notifications for booking and event changes via webhooks

Webhooks allow your application to receive real-time notifications when events occur in Cal.com. Instead of polling the API, webhooks push data to your server as events happen.

## Overview

Cal.com webhooks deliver HTTP POST requests to your specified URL when events occur:

* **Real-time notifications** for booking events
* **Automatic retries** with exponential backoff
* **Signature verification** for security
* **Custom payload templates** for flexibility
* **Multiple webhook versions** for backward compatibility

## Webhook Types

Webhooks can be created at different scopes:

<CardGroup cols={3}>
  <Card title="User Webhooks" icon="user">
    Triggered for events related to a specific user
  </Card>

  <Card title="Event Type Webhooks" icon="calendar">
    Triggered for bookings of a specific event type
  </Card>

  <Card title="Team Webhooks" icon="users">
    Triggered for events within a team
  </Card>
</CardGroup>

## Webhook Events

Cal.com supports webhooks for the following event triggers:

### Core Booking Events

| Event                       | Description                   |
| --------------------------- | ----------------------------- |
| `BOOKING_CREATED`           | New booking created           |
| `BOOKING_RESCHEDULED`       | Booking time changed          |
| `BOOKING_CANCELLED`         | Booking cancelled             |
| `BOOKING_REJECTED`          | Booking request rejected      |
| `BOOKING_REQUESTED`         | New booking requires approval |
| `BOOKING_PAID`              | Payment completed for booking |
| `BOOKING_PAYMENT_INITIATED` | Payment process started       |
| `BOOKING_NO_SHOW_UPDATED`   | No-show status updated        |

### Meeting Events

| Event                               | Description                 |
| ----------------------------------- | --------------------------- |
| `MEETING_STARTED`                   | Video meeting started       |
| `MEETING_ENDED`                     | Video meeting ended         |
| `INSTANT_MEETING`                   | Instant meeting created     |
| `RECORDING_READY`                   | Meeting recording available |
| `RECORDING_TRANSCRIPTION_GENERATED` | Transcription completed     |

### Other Events

| Event                            | Description                            |
| -------------------------------- | -------------------------------------- |
| `OOO_CREATED`                    | Out of office entry created            |
| `FORM_SUBMITTED`                 | Routing form submitted with booking    |
| `FORM_SUBMITTED_NO_EVENT`        | Routing form submitted without booking |
| `ROUTING_FORM_FALLBACK_HIT`      | No routing rules matched               |
| `AFTER_HOSTS_CAL_VIDEO_NO_SHOW`  | Host no-show detected                  |
| `AFTER_GUESTS_CAL_VIDEO_NO_SHOW` | Guest no-show detected                 |
| `DELEGATION_CREDENTIAL_ERROR`    | Credential delegation error            |
| `WRONG_ASSIGNMENT_REPORT`        | Assignment error reported              |

## Creating Webhooks

### Create a User Webhook

**Endpoint:** `POST /v2/webhooks`

**Request:**

```bash theme={null}
curl -X POST https://api.cal.com/v2/webhooks \
  -H "Authorization: Bearer cal_live_xxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "subscriberUrl": "https://your-app.com/webhooks/cal",
    "active": true,
    "triggers": [
      "BOOKING_CREATED",
      "BOOKING_RESCHEDULED",
      "BOOKING_CANCELLED"
    ],
    "secret": "your_webhook_secret"
  }'
```

**Response:**

```json theme={null}
{
  "status": "success",
  "data": {
    "id": "webhook_123",
    "subscriberUrl": "https://your-app.com/webhooks/cal",
    "active": true,
    "triggers": [
      "BOOKING_CREATED",
      "BOOKING_RESCHEDULED",
      "BOOKING_CANCELLED"
    ],
    "secret": "your_webhook_secret",
    "version": "2021-10-20",
    "createdAt": "2024-03-15T10:00:00Z"
  }
}
```

### Create an Event Type Webhook

**Endpoint:** `POST /v2/event-types/:eventTypeId/webhooks`

```bash theme={null}
curl -X POST https://api.cal.com/v2/event-types/123/webhooks \
  -H "Authorization: Bearer cal_live_xxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "subscriberUrl": "https://your-app.com/webhooks/event-type",
    "active": true,
    "triggers": ["BOOKING_CREATED", "BOOKING_CANCELLED"]
  }'
```

### Create a Team Webhook

**Endpoint:** `POST /v2/teams/:teamId/event-types/:eventTypeId/webhooks`

```bash theme={null}
curl -X POST https://api.cal.com/v2/teams/456/event-types/123/webhooks \
  -H "Authorization: Bearer cal_live_xxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "subscriberUrl": "https://your-app.com/webhooks/team",
    "active": true,
    "triggers": ["BOOKING_CREATED"]
  }'
```

## Managing Webhooks

### List Webhooks

```bash theme={null}
GET /v2/webhooks
```

**Response:**

```json theme={null}
{
  "status": "success",
  "data": [
    {
      "id": "webhook_123",
      "subscriberUrl": "https://your-app.com/webhooks/cal",
      "active": true,
      "triggers": ["BOOKING_CREATED", "BOOKING_CANCELLED"],
      "version": "2021-10-20"
    }
  ]
}
```

### Get Webhook Details

```bash theme={null}
GET /v2/webhooks/:webhookId
```

### Update Webhook

```bash theme={null}
PATCH /v2/webhooks/:webhookId
```

**Request:**

```bash theme={null}
curl -X PATCH https://api.cal.com/v2/webhooks/webhook_123 \
  -H "Authorization: Bearer cal_live_xxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "active": false,
    "triggers": ["BOOKING_CREATED"]
  }'
```

### Delete Webhook

```bash theme={null}
DELETE /v2/webhooks/:webhookId
```

## Webhook Payload

### Standard Payload Structure

All webhooks follow this structure:

```json theme={null}
{
  "triggerEvent": "BOOKING_CREATED",
  "createdAt": "2024-03-15T10:00:00Z",
  "payload": {
    // Event-specific data
  }
}
```

### Booking Created Payload

```json theme={null}
{
  "triggerEvent": "BOOKING_CREATED",
  "createdAt": "2024-03-15T10:00:00Z",
  "payload": {
    "bookingId": 123,
    "uid": "booking_abc123",
    "title": "30 Minute Meeting between John Doe and Jane Smith",
    "type": "30min",
    "description": "Quick sync meeting",
    "startTime": "2024-03-20T15:00:00Z",
    "endTime": "2024-03-20T15:30:00Z",
    "status": "ACCEPTED",
    "organizer": {
      "name": "John Doe",
      "email": "john@example.com",
      "timeZone": "America/New_York",
      "utcOffset": -240
    },
    "attendees": [
      {
        "name": "Jane Smith",
        "email": "jane@example.com",
        "timeZone": "America/Los_Angeles",
        "utcOffset": -420,
        "firstName": "Jane",
        "lastName": "Smith"
      }
    ],
    "location": "https://meet.google.com/abc-defg-hij",
    "responses": {
      "name": {
        "label": "your_name",
        "value": "Jane Smith"
      },
      "email": {
        "label": "email_address",
        "value": "jane@example.com"
      }
    },
    "eventTitle": "30 Minute Meeting",
    "eventDescription": "A brief meeting to discuss project updates",
    "price": 0,
    "currency": "usd",
    "length": 30,
    "requiresConfirmation": false
  }
}
```

### Booking Cancelled Payload

```json theme={null}
{
  "triggerEvent": "BOOKING_CANCELLED",
  "createdAt": "2024-03-15T11:00:00Z",
  "payload": {
    "bookingId": 123,
    "uid": "booking_abc123",
    "title": "30 Minute Meeting",
    "status": "CANCELLED",
    "cancelledBy": "john@example.com",
    "cancellationReason": "Conflict with another meeting",
    "organizer": {
      "name": "John Doe",
      "email": "john@example.com"
    },
    "attendees": [
      {
        "name": "Jane Smith",
        "email": "jane@example.com"
      }
    ]
  }
}
```

### Booking Rescheduled Payload

```json theme={null}
{
  "triggerEvent": "BOOKING_RESCHEDULED",
  "createdAt": "2024-03-15T12:00:00Z",
  "payload": {
    "bookingId": 123,
    "uid": "booking_abc123",
    "title": "30 Minute Meeting",
    "startTime": "2024-03-21T15:00:00Z",
    "endTime": "2024-03-21T15:30:00Z",
    "rescheduleId": 456,
    "rescheduleUid": "booking_def456",
    "rescheduleStartTime": "2024-03-20T15:00:00Z",
    "rescheduleEndTime": "2024-03-20T15:30:00Z",
    "rescheduledBy": "jane@example.com",
    "organizer": {
      "name": "John Doe",
      "email": "john@example.com"
    },
    "attendees": [
      {
        "name": "Jane Smith",
        "email": "jane@example.com"
      }
    ]
  }
}
```

### Meeting Started Payload

```json theme={null}
{
  "triggerEvent": "MEETING_STARTED",
  "createdAt": "2024-03-20T15:00:00Z",
  "payload": {
    "booking": {
      "id": 123,
      "startTime": "2024-03-20T15:00:00Z",
      "endTime": "2024-03-20T15:30:00Z",
      "title": "30 Minute Meeting",
      "status": "ACCEPTED",
      "user": {
        "name": "John Doe",
        "email": "john@example.com",
        "timeZone": "America/New_York"
      },
      "attendees": [
        {
          "name": "Jane Smith",
          "email": "jane@example.com"
        }
      ]
    }
  }
}
```

## Webhook Signature Verification

Webhooks include a signature in the `X-Cal-Signature-256` header for verification.

### Verifying Webhook Signatures

<CodeGroup>
  ```javascript Node.js theme={null}
  const crypto = require('crypto');

  function verifyWebhookSignature(payload, signature, secret) {
    const expectedSignature = crypto
      .createHmac('sha256', secret)
      .update(JSON.stringify(payload))
      .digest('hex');
    
    return crypto.timingSafeEqual(
      Buffer.from(signature),
      Buffer.from(expectedSignature)
    );
  }

  // Express middleware
  app.post('/webhooks/cal', (req, res) => {
    const signature = req.headers['x-cal-signature-256'];
    const secret = process.env.WEBHOOK_SECRET;
    
    if (!verifyWebhookSignature(req.body, signature, secret)) {
      return res.status(401).send('Invalid signature');
    }
    
    // Process webhook
    const { triggerEvent, payload } = req.body;
    
    console.log(`Received ${triggerEvent}:`, payload);
    
    res.status(200).send('OK');
  });
  ```

  ```python Python theme={null}
  import hmac
  import hashlib
  import json
  from flask import Flask, request

  app = Flask(__name__)

  def verify_webhook_signature(payload, signature, secret):
      expected_signature = hmac.new(
          secret.encode('utf-8'),
          json.dumps(payload).encode('utf-8'),
          hashlib.sha256
      ).hexdigest()
      
      return hmac.compare_digest(signature, expected_signature)

  @app.route('/webhooks/cal', methods=['POST'])
  def handle_webhook():
      signature = request.headers.get('X-Cal-Signature-256')
      secret = os.environ.get('WEBHOOK_SECRET')
      
      if not verify_webhook_signature(request.json, signature, secret):
          return 'Invalid signature', 401
      
      # Process webhook
      trigger_event = request.json['triggerEvent']
      payload = request.json['payload']
      
      print(f'Received {trigger_event}:', payload)
      
      return 'OK', 200
  ```

  ```php PHP theme={null}
  <?php

  function verifyWebhookSignature($payload, $signature, $secret) {
      $expectedSignature = hash_hmac(
          'sha256',
          json_encode($payload),
          $secret
      );
      
      return hash_equals($signature, $expectedSignature);
  }

  // Webhook handler
  $signature = $_SERVER['HTTP_X_CAL_SIGNATURE_256'];
  $secret = getenv('WEBHOOK_SECRET');
  $payload = json_decode(file_get_contents('php://input'), true);

  if (!verifyWebhookSignature($payload, $signature, $secret)) {
      http_response_code(401);
      die('Invalid signature');
  }

  // Process webhook
  $triggerEvent = $payload['triggerEvent'];
  $data = $payload['payload'];

  echo 'OK';
  ?>
  ```
</CodeGroup>

## Custom Payload Templates

You can customize webhook payloads using templates:

```bash theme={null}
curl -X POST https://api.cal.com/v2/webhooks \
  -H "Authorization: Bearer cal_live_xxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "subscriberUrl": "https://your-app.com/webhooks",
    "active": true,
    "triggers": ["BOOKING_CREATED"],
    "payloadTemplate": "{
      \"event\": \"{{type}}\",
      \"booking\": {
        \"id\": {{bookingId}},
        \"title\": \"{{title}}\",
        \"start\": \"{{startTime}}\",
        \"end\": \"{{endTime}}\"
      },
      \"organizer\": \"{{organizer.name}}\",
      \"attendee\": \"{{attendees.0.name}}\"
    }"
  }'
```

**Available template variables:**

* `{{type}}` - Event type
* `{{title}}` - Booking title
* `{{bookingId}}` - Booking ID
* `{{startTime}}` - Start time
* `{{endTime}}` - End time
* `{{organizer.name}}` - Organizer name
* `{{organizer.email}}` - Organizer email
* `{{attendees.0.name}}` - First attendee name
* `{{attendees.0.email}}` - First attendee email

## Webhook Delivery

### Delivery Behavior

* **Timeout**: 30 seconds per attempt
* **Retries**: Up to 5 attempts with exponential backoff
* **Backoff**: 1s, 2s, 4s, 8s, 16s
* **Success**: Any 2xx status code
* **Failure**: Non-2xx status code or timeout

### Responding to Webhooks

Your endpoint should:

* Respond with a 2xx status code within 30 seconds
* Process webhooks asynchronously if needed
* Return quickly to avoid timeouts

```javascript theme={null}
app.post('/webhooks/cal', async (req, res) => {
  // Respond immediately
  res.status(200).send('OK');
  
  // Process webhook asynchronously
  processWebhookAsync(req.body).catch(console.error);
});

async function processWebhookAsync(data) {
  // Long-running processing here
  await updateDatabase(data);
  await sendNotifications(data);
}
```

## Webhook Versions

Webhooks support versioning for backward compatibility:

### Version 2021-10-20 (Current)

The current webhook payload format. See payload examples above.

### Specifying Webhook Version

```json theme={null}
{
  "subscriberUrl": "https://your-app.com/webhooks",
  "active": true,
  "triggers": ["BOOKING_CREATED"],
  "version": "2021-10-20"
}
```

## Testing Webhooks

### Using Webhook Testing Tools

1. **webhook.site** - Get a temporary URL for testing
2. **ngrok** - Expose your local server
3. **Postman** - Mock webhook server

### Testing with ngrok

```bash theme={null}
# Start ngrok
ngrok http 3000

# Use the ngrok URL in your webhook configuration
https://abc123.ngrok.io/webhooks/cal
```

### Test Webhook Endpoint

```javascript theme={null}
const express = require('express');
const app = express();

app.use(express.json());

app.post('/webhooks/cal', (req, res) => {
  console.log('Webhook received:');
  console.log(JSON.stringify(req.body, null, 2));
  
  res.status(200).send('OK');
});

app.listen(3000, () => {
  console.log('Webhook server listening on port 3000');
});
```

## Error Handling

### Webhook Delivery Failures

If webhook delivery fails after all retries:

* The webhook remains active
* Future events will continue to trigger
* Check webhook logs in Cal.com dashboard

### Common Issues

<AccordionGroup>
  <Accordion title="Timeout Errors">
    * Ensure your endpoint responds within 30 seconds
    * Process webhooks asynchronously
    * Return 200 status immediately
  </Accordion>

  <Accordion title="SSL Certificate Errors">
    * Use valid SSL certificates
    * Don't use self-signed certificates
    * Ensure certificate is not expired
  </Accordion>

  <Accordion title="Authentication Errors">
    * Verify webhook signature correctly
    * Use the secret provided when creating webhook
    * Check signature header name: `X-Cal-Signature-256`
  </Accordion>

  <Accordion title="Duplicate Events">
    * Implement idempotency using booking ID
    * Track processed webhook IDs
    * Handle duplicate deliveries gracefully
  </Accordion>
</AccordionGroup>

## Best Practices

<AccordionGroup>
  <Accordion title="Implement Idempotency">
    Handle duplicate webhook deliveries:

    ```javascript theme={null}
    const processedWebhooks = new Set();

    app.post('/webhooks/cal', async (req, res) => {
      const { payload } = req.body;
      const webhookId = `${payload.bookingId}-${req.body.createdAt}`;
      
      if (processedWebhooks.has(webhookId)) {
        return res.status(200).send('Already processed');
      }
      
      processedWebhooks.add(webhookId);
      
      // Process webhook
      await processBooking(payload);
      
      res.status(200).send('OK');
    });
    ```
  </Accordion>

  <Accordion title="Log All Webhooks">
    Maintain webhook logs for debugging:

    ```javascript theme={null}
    app.post('/webhooks/cal', async (req, res) => {
      // Log webhook
      await logWebhook({
        timestamp: new Date(),
        triggerEvent: req.body.triggerEvent,
        payload: req.body.payload,
        headers: req.headers
      });
      
      // Process webhook
      await processWebhook(req.body);
      
      res.status(200).send('OK');
    });
    ```
  </Accordion>

  <Accordion title="Monitor Webhook Health">
    Track webhook delivery success rates:

    * Monitor response times
    * Alert on repeated failures
    * Track processing errors
    * Review webhook logs regularly
  </Accordion>

  <Accordion title="Secure Your Endpoint">
    * Always verify webhook signatures
    * Use HTTPS only
    * Validate payload structure
    * Rate limit webhook endpoints
  </Accordion>
</AccordionGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="Authentication" icon="key" href="/api/authentication">
    Secure your webhook endpoints
  </Card>

  <Card title="API Reference" icon="book" href="/api/reference">
    Browse webhook endpoints
  </Card>

  <Card title="Rate Limits" icon="gauge" href="/api/rate-limits">
    Understand webhook rate limits
  </Card>

  <Card title="Examples" icon="code" href="/examples/webhooks">
    See webhook implementation examples
  </Card>
</CardGroup>
