Corsair
PluginsCal.com

Cal.com

Integrate Cal.com scheduling and booking management

Quick Start

Install the plugin:

pnpm install @corsair-dev/cal

Add the Cal.com plugin to your Corsair instance:

corsair.ts
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-key

For webhook signature verification:

pnpm corsair setup --cal webhook_signature=your-webhook-secret

Alternatively, provide credentials directly in the config:

corsair.ts
cal({
    key: process.env.CAL_API_KEY,
    webhookSecret: process.env.CAL_WEBHOOK_SECRET,
})

See Authentication for details on managing credentials.

Options

OptionTypeDescription
authType'api_key'Authentication method (defaults to 'api_key')
keystringAPI key (optional, uses database if not provided)
webhookSecretstringWebhook secret for x-cal-signature-256 verification
hooksobjectEndpoint hooks for custom logic
webhookHooksobjectWebhook hooks for event handling
errorHandlersobjectCustom error handlers
permissionsobjectPermission 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:

app/api/webhook/route.ts
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

corsair.ts
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, 
                                }, 
                            }); 
                        },
                    },
                },
            },
        }),
    ],
});
inngest/functions.ts
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

corsair.ts
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(),
    ],
});
inngest/functions.ts
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() },
            });
        }
    }
);