Cal.com
Integrate Cal.com scheduling and booking management
Quick Start
Install the plugin:
pnpm install @corsair-dev/calAdd the Cal.com plugin to your Corsair instance:
import { createCorsair } from "corsair";
import { cal } from "@corsair-dev/cal";
export const corsair = createCorsair({
plugins: [
cal(),
],
});Once configured, you can manage bookings:
// List all bookings
const bookings = await corsair.cal.api.bookings.list({});Authentication
Supported Auth Types
The Cal.com plugin supports:
api_key(default) - Use a Cal.com API key
Default Auth Type
If no authType is specified, the plugin defaults to api_key.
Configuring API Key Authentication
Store credentials with the CLI:
pnpm corsair setup --cal api_key=your-api-keyFor webhook signature verification:
pnpm corsair setup --cal webhook_signature=your-webhook-secretAlternatively, provide credentials directly in the config:
cal({
key: process.env.CAL_API_KEY,
webhookSecret: process.env.CAL_WEBHOOK_SECRET,
})See Authentication for details on managing credentials.
Options
| Option | Type | Description |
|---|---|---|
authType | 'api_key' | Authentication method (defaults to 'api_key') |
key | string | API key (optional, uses database if not provided) |
webhookSecret | string | Webhook secret for x-cal-signature-256 verification |
hooks | object | Endpoint hooks for custom logic |
webhookHooks | object | Webhook hooks for event handling |
errorHandlers | object | Custom error handlers |
permissions | object | Permission configuration for AI agent access |
Hooks
cal({
hooks: {
bookings: {
create: {
before: async (ctx, input) => {
console.log("Creating booking:", input.eventTypeId);
return { ctx, input };
},
},
},
},
})See Hooks for complete documentation.
Error Handling
The plugin includes built-in error handlers for common scenarios. For complete documentation, see the Error Handlers reference.
Usage
Accessing the API
// Get a specific booking
const booking = await corsair.cal.api.bookings.get({
uid: "booking-uid-123",
});
// Create a booking
await corsair.cal.api.bookings.create({
eventTypeId: 12345,
start: "2024-02-15T10:00:00Z",
responses: {
name: "John Doe",
email: "john@example.com",
},
});
// Cancel a booking
await corsair.cal.api.bookings.cancel({
uid: "booking-uid-123",
reason: "Schedule conflict",
});See API Endpoints for the complete reference.
Webhooks
Cal.com sends booking lifecycle events to your webhook endpoint:
import { processWebhook } from "corsair";
import { corsair } from "@/server/corsair";
export async function POST(request: Request) {
const headers = Object.fromEntries(request.headers);
const body = await request.json();
const result = await processWebhook(corsair, headers, body);
return result.response;
}See Webhooks for all available events.
Database Access
const upcomingBookings = await corsair.cal.db.bookings.search({
data: { status: "ACCEPTED" },
});See Database for the complete schema.
Multi-Tenancy
const tenant = corsair.withTenant("user-123");
const bookings = await tenant.cal.api.bookings.list({
status: "upcoming",
});Examples
Example 1: Send Confirmation Email on New Booking
import { inngest } from "./inngest";
export const corsair = createCorsair({
plugins: [
cal({
webhookHooks: {
bookings: {
bookingCreated: {
after: async (ctx, result) => {
await inngest.send({
name: "cal/booking-created",
data: {
tenantId: ctx.tenantId,
bookingUid: result.data.uid,
attendeeName: result.data.attendees?.[0]?.name,
attendeeEmail: result.data.attendees?.[0]?.email,
startTime: result.data.startTime,
},
});
},
},
},
},
}),
],
});export const sendBookingConfirmation = inngest.createFunction(
{ id: "send-booking-confirmation" },
{ event: "cal/booking-created" },
async ({ event }) => {
const tenant = corsair.withTenant(event.data.tenantId);
await tenant.resend.api.emails.send({
from: "bookings@example.com",
to: event.data.attendeeEmail,
subject: "Your booking is confirmed",
text: `Hi ${event.data.attendeeName}, your meeting is scheduled for ${event.data.startTime}.`,
});
}
);Example 2: Add to CRM on Meeting Ended
export const corsair = createCorsair({
plugins: [
cal({
webhookHooks: {
bookings: {
meetingEnded: {
after: async (ctx, result) => {
await inngest.send({
name: "cal/meeting-ended",
data: {
tenantId: ctx.tenantId,
bookingUid: result.data.uid,
attendeeEmail: result.data.attendees?.[0]?.email,
},
});
},
},
},
},
}),
hubspot(),
],
});export const updateCrmAfterMeeting = inngest.createFunction(
{ id: "update-crm-after-meeting" },
{ event: "cal/meeting-ended" },
async ({ event }) => {
const tenant = corsair.withTenant(event.data.tenantId);
const contacts = await tenant.hubspot.api.contacts.search({
filterGroups: [{
filters: [{ propertyName: "email", operator: "EQ", value: event.data.attendeeEmail }],
}],
});
if (contacts.data.results[0]) {
await tenant.hubspot.api.contacts.update({
contactId: contacts.data.results[0].id,
properties: { last_meeting_date: new Date().toISOString() },
});
}
}
);