git clone https://github.com/ComeOnOliver/skillshub
T=$(mktemp -d) && git clone --depth=1 https://github.com/ComeOnOliver/skillshub "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/TerminalSkills/skills/cal-com" ~/.claude/skills/comeonoliver-skillshub-cal-com && rm -rf "$T"
skills/TerminalSkills/skills/cal-com/SKILL.mdCal.com — Scheduling Infrastructure
Overview
Cal.com, the open-source scheduling platform for building booking and appointment systems. Helps developers integrate Cal.com's embed widgets, REST API, and webhooks to add scheduling capabilities to their applications.
Instructions
Embed Widget
Add a booking widget to any website:
// src/components/BookingWidget.tsx — Embed Cal.com scheduling in a React app import Cal, { getCalApi } from "@calcom/embed-react"; import { useEffect } from "react"; export function BookingWidget() { useEffect(() => { (async () => { const cal = await getCalApi(); // Configure the embed appearance cal("ui", { theme: "light", styles: { branding: { brandColor: "#6366f1" } }, hideEventTypeDetails: false, layout: "month_view", // "month_view" | "week_view" | "column_view" }); })(); }, []); return ( <Cal calLink="your-team/discovery-call" // Your Cal.com event link style={{ width: "100%", height: "100%", overflow: "scroll" }} config={{ layout: "month_view", name: "Customer Name", // Pre-fill guest name email: "customer@example.com", // Pre-fill guest email notes: "Interested in Enterprise plan", }} /> ); } // Floating button that opens calendar in a popup export function BookingButton() { useEffect(() => { (async () => { const cal = await getCalApi(); cal("floatingButton", { calLink: "your-team/discovery-call", buttonText: "Book a Demo", buttonColor: "#6366f1", buttonTextColor: "#ffffff", buttonPosition: "bottom-right", }); })(); }, []); return null; // The floating button renders itself }
REST API Integration
Manage bookings, event types, and availability programmatically:
// src/cal/client.ts — Cal.com API client for server-side operations const CAL_API_URL = "https://api.cal.com/v2"; const CAL_API_KEY = process.env.CAL_API_KEY!; async function calFetch(path: string, options?: RequestInit) { const response = await fetch(`${CAL_API_URL}${path}`, { ...options, headers: { "Content-Type": "application/json", "cal-api-version": "2024-08-13", // Pin API version for stability Authorization: `Bearer ${CAL_API_KEY}`, ...options?.headers, }, }); if (!response.ok) { const error = await response.json(); throw new Error(`Cal.com API error: ${error.message}`); } return response.json(); } // Create a new event type (e.g., "30-min Discovery Call") async function createEventType(params: { title: string; slug: string; lengthInMinutes: number; description?: string; locations?: { type: string; link?: string }[]; }) { return calFetch("/event-types", { method: "POST", body: JSON.stringify({ title: params.title, slug: params.slug, lengthInMinutes: params.lengthInMinutes, description: params.description, locations: params.locations ?? [ { type: "integrations:google:meet" }, // Default: Google Meet ], bookingFields: [ { name: "company", type: "text", label: "Company Name", required: true }, { name: "teamSize", type: "select", label: "Team Size", options: ["1-10", "11-50", "51-200", "200+"], required: true }, ], }), }); } // Get available time slots for a date range async function getAvailability(params: { eventTypeId: number; startTime: string; // ISO 8601 endTime: string; timeZone: string; }) { const query = new URLSearchParams({ eventTypeId: String(params.eventTypeId), startTime: params.startTime, endTime: params.endTime, timeZone: params.timeZone, }); return calFetch(`/slots?${query}`); } // Create a booking async function createBooking(params: { eventTypeId: number; start: string; // ISO 8601 datetime attendee: { name: string; email: string; timeZone: string }; metadata?: Record<string, string>; }) { return calFetch("/bookings", { method: "POST", body: JSON.stringify({ eventTypeId: params.eventTypeId, start: params.start, attendee: params.attendee, metadata: params.metadata, language: "en", }), }); } // Cancel a booking async function cancelBooking(bookingUid: string, reason?: string) { return calFetch(`/bookings/${bookingUid}/cancel`, { method: "POST", body: JSON.stringify({ reason }), }); } // Reschedule a booking async function rescheduleBooking(bookingUid: string, newStart: string) { return calFetch(`/bookings/${bookingUid}/reschedule`, { method: "POST", body: JSON.stringify({ start: newStart }), }); }
Webhooks
React to booking events in real time:
// app/api/cal-webhook/route.ts — Handle Cal.com webhook events import { NextRequest, NextResponse } from "next/server"; import crypto from "crypto"; const WEBHOOK_SECRET = process.env.CAL_WEBHOOK_SECRET!; // Verify the webhook signature to ensure it's from Cal.com function verifySignature(payload: string, signature: string): boolean { const expected = crypto .createHmac("sha256", WEBHOOK_SECRET) .update(payload) .digest("hex"); return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected)); } export async function POST(request: NextRequest) { const payload = await request.text(); const signature = request.headers.get("x-cal-signature-256") ?? ""; if (!verifySignature(payload, signature)) { return NextResponse.json({ error: "Invalid signature" }, { status: 401 }); } const event = JSON.parse(payload); switch (event.triggerEvent) { case "BOOKING_CREATED": // New booking — send confirmation, update CRM, notify sales await handleNewBooking({ bookingId: event.payload.bookingId, attendeeName: event.payload.attendees[0]?.name, attendeeEmail: event.payload.attendees[0]?.email, startTime: event.payload.startTime, eventType: event.payload.eventTitle, metadata: event.payload.metadata, }); break; case "BOOKING_CANCELLED": // Booking cancelled — update CRM, free up resources await handleCancellation({ bookingId: event.payload.bookingId, reason: event.payload.cancellationReason, }); break; case "BOOKING_RESCHEDULED": // Booking moved — update calendar integrations await handleReschedule({ bookingId: event.payload.bookingId, oldTime: event.payload.previousStartTime, newTime: event.payload.startTime, }); break; case "MEETING_ENDED": // Meeting finished — trigger follow-up sequence await triggerFollowUp(event.payload.bookingId); break; } return NextResponse.json({ received: true }); } async function handleNewBooking(booking: any) { // Example: Create a deal in your CRM await crm.createDeal({ name: `Demo: ${booking.attendeeName}`, contact: booking.attendeeEmail, stage: "discovery", source: "website-booking", metadata: booking.metadata, }); // Notify the sales team via Slack await slack.postMessage({ channel: "#sales-notifications", text: `📅 New ${booking.eventType} booked!\n👤 ${booking.attendeeName} (${booking.attendeeEmail})\n🕐 ${new Date(booking.startTime).toLocaleString()}`, }); }
Managing Team Availability
Configure round-robin scheduling and team calendars:
// src/cal/team-setup.ts — Configure team scheduling rules async function setupTeamScheduling() { // Create a team event type with round-robin assignment const eventType = await calFetch("/event-types", { method: "POST", body: JSON.stringify({ title: "Product Demo", slug: "product-demo", lengthInMinutes: 45, schedulingType: "ROUND_ROBIN", // Rotate among team members hosts: [ { userId: 101, isFixed: false }, // Participates in rotation { userId: 102, isFixed: false }, { userId: 103, isFixed: true }, // Always included (e.g., sales engineer) ], beforeEventBuffer: 15, // 15-min buffer before meetings afterEventBuffer: 10, // 10-min buffer after minimumBookingNotice: 120, // Minimum 2 hours notice slotInterval: 15, // Show slots every 15 minutes locations: [ { type: "integrations:zoom" }, // Auto-create Zoom meeting ], }), }); // Set working hours for the team await calFetch("/schedules", { method: "POST", body: JSON.stringify({ name: "Business Hours", timeZone: "America/New_York", isDefault: true, availability: [ // Monday-Friday 9 AM to 5 PM { days: [1, 2, 3, 4, 5], startTime: "09:00", endTime: "17:00" }, ], // Block specific dates (holidays, company events) dateOverrides: [ { date: "2026-12-25", startTime: null, endTime: null }, // Christmas — unavailable { date: "2026-12-31", startTime: "09:00", endTime: "12:00" }, // NYE — morning only ], }), }); }
Installation
# React embed npm install @calcom/embed-react # For self-hosted Cal.com (Docker) git clone https://github.com/calcom/cal.com.git cd cal.com cp .env.example .env docker compose up -d
Examples
Example 1: Setting up Cal Com with a custom configuration
User request:
I just installed Cal Com. Help me configure it for my TypeScript + React workflow with my preferred keybindings.
The agent creates the configuration file with TypeScript-aware settings, configures relevant plugins/extensions for React development, sets up keyboard shortcuts matching the user's preferences, and verifies the setup works correctly.
Example 2: Extending Cal Com with custom functionality
User request:
I want to add a custom rest api integration to Cal Com. How do I build one?
The agent scaffolds the extension/plugin project, implements the core functionality following Cal Com's API patterns, adds configuration options, and provides testing instructions to verify it works end-to-end.
Guidelines
- Use embed for public booking — The React embed handles time zones, availability, and confirmations automatically
- Pin API versions — Include
header to avoid breaking changes when Cal.com updatescal-api-version - Verify webhook signatures — Always validate
to prevent spoofed eventsx-cal-signature-256 - Buffer time between meetings — Set
andbeforeEventBuffer
to prevent back-to-back meetingsafterEventBuffer - Pre-fill known data — Pass name, email, and context via embed config to reduce friction for returning users
- Use metadata for attribution — Pass UTM params, plan interest, or referral codes through booking metadata
- Handle all webhook events — Don't just handle BOOKING_CREATED; cancellations and reschedules need cleanup too
- Self-host for compliance — If you need data residency (GDPR, HIPAA), self-host Cal.com with Docker