diff --git a/apps/sim/blocks/blocks/calcom.ts b/apps/sim/blocks/blocks/calcom.ts index 7a3d42f19..e8dddc9ec 100644 --- a/apps/sim/blocks/blocks/calcom.ts +++ b/apps/sim/blocks/blocks/calcom.ts @@ -469,6 +469,12 @@ Return ONLY valid JSON - no explanations.`, ...getTrigger('calcom_booking_created').subBlocks, ...getTrigger('calcom_booking_cancelled').subBlocks, ...getTrigger('calcom_booking_rescheduled').subBlocks, + ...getTrigger('calcom_booking_requested').subBlocks, + ...getTrigger('calcom_booking_rejected').subBlocks, + ...getTrigger('calcom_booking_paid').subBlocks, + ...getTrigger('calcom_meeting_ended').subBlocks, + ...getTrigger('calcom_recording_ready').subBlocks, + ...getTrigger('calcom_webhook').subBlocks, ], tools: { access: [ @@ -656,6 +662,16 @@ Return ONLY valid JSON - no explanations.`, }, triggers: { enabled: true, - available: ['calcom_booking_created', 'calcom_booking_cancelled', 'calcom_booking_rescheduled'], + available: [ + 'calcom_booking_created', + 'calcom_booking_cancelled', + 'calcom_booking_rescheduled', + 'calcom_booking_requested', + 'calcom_booking_rejected', + 'calcom_booking_paid', + 'calcom_meeting_ended', + 'calcom_recording_ready', + 'calcom_webhook', + ], }, } diff --git a/apps/sim/lib/auth/auth.ts b/apps/sim/lib/auth/auth.ts index a35e24b7c..707f18be6 100644 --- a/apps/sim/lib/auth/auth.ts +++ b/apps/sim/lib/auth/auth.ts @@ -2547,7 +2547,6 @@ export const auth = betterAuth({ { providerId: 'calcom', clientId: env.CALCOM_CLIENT_ID as string, - clientSecret: env.CALCOM_CLIENT_SECRET as string, authorizationUrl: 'https://app.cal.com/auth/oauth2/authorize', tokenUrl: 'https://app.cal.com/api/auth/oauth/token', scopes: [], diff --git a/apps/sim/lib/core/config/env.ts b/apps/sim/lib/core/config/env.ts index 113b6d521..86ccbc2c8 100644 --- a/apps/sim/lib/core/config/env.ts +++ b/apps/sim/lib/core/config/env.ts @@ -244,7 +244,6 @@ export const env = createEnv({ SPOTIFY_CLIENT_ID: z.string().optional(), // Spotify OAuth client ID SPOTIFY_CLIENT_SECRET: z.string().optional(), // Spotify OAuth client secret CALCOM_CLIENT_ID: z.string().optional(), // Cal.com OAuth client ID - CALCOM_CLIENT_SECRET: z.string().optional(), // Cal.com OAuth client secret // E2B Remote Code Execution E2B_ENABLED: z.string().optional(), // Enable E2B remote code execution diff --git a/apps/sim/lib/oauth/oauth.ts b/apps/sim/lib/oauth/oauth.ts index eed4f43a8..5e48a879c 100644 --- a/apps/sim/lib/oauth/oauth.ts +++ b/apps/sim/lib/oauth/oauth.ts @@ -878,14 +878,14 @@ function getProviderAuthConfig(provider: string): ProviderAuthConfig { } } case 'calcom': { - const { clientId, clientSecret } = getCredentials( - env.CALCOM_CLIENT_ID, - env.CALCOM_CLIENT_SECRET - ) + const clientId = env.CALCOM_CLIENT_ID + if (!clientId) { + throw new Error('Missing CALCOM_CLIENT_ID') + } return { tokenEndpoint: 'https://app.cal.com/api/auth/oauth/refreshToken', clientId, - clientSecret, + clientSecret: '', useBasicAuth: false, supportsRefreshTokenRotation: true, } diff --git a/apps/sim/triggers/calcom/booking_paid.ts b/apps/sim/triggers/calcom/booking_paid.ts new file mode 100644 index 000000000..0492e3a6c --- /dev/null +++ b/apps/sim/triggers/calcom/booking_paid.ts @@ -0,0 +1,35 @@ +import { CalComIcon } from '@/components/icons' +import { buildTriggerSubBlocks } from '@/triggers' +import { + buildPaidOutputs, + calcomSetupInstructions, + calcomTriggerOptions, + calcomWebhookSecretField, +} from '@/triggers/calcom/utils' +import type { TriggerConfig } from '@/triggers/types' + +export const calcomBookingPaidTrigger: TriggerConfig = { + id: 'calcom_booking_paid', + name: 'CalCom Booking Paid', + provider: 'calcom', + description: 'Trigger workflow when payment is completed for a paid booking', + version: '1.0.0', + icon: CalComIcon, + + subBlocks: buildTriggerSubBlocks({ + triggerId: 'calcom_booking_paid', + triggerOptions: calcomTriggerOptions, + setupInstructions: calcomSetupInstructions('paid'), + extraFields: [calcomWebhookSecretField('calcom_booking_paid')], + }), + + outputs: buildPaidOutputs(), + + webhook: { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-Cal-Signature-256': 'sha256=...', + }, + }, +} diff --git a/apps/sim/triggers/calcom/booking_rejected.ts b/apps/sim/triggers/calcom/booking_rejected.ts new file mode 100644 index 000000000..ae2413b7c --- /dev/null +++ b/apps/sim/triggers/calcom/booking_rejected.ts @@ -0,0 +1,35 @@ +import { CalComIcon } from '@/components/icons' +import { buildTriggerSubBlocks } from '@/triggers' +import { + buildRejectedOutputs, + calcomSetupInstructions, + calcomTriggerOptions, + calcomWebhookSecretField, +} from '@/triggers/calcom/utils' +import type { TriggerConfig } from '@/triggers/types' + +export const calcomBookingRejectedTrigger: TriggerConfig = { + id: 'calcom_booking_rejected', + name: 'CalCom Booking Rejected', + provider: 'calcom', + description: 'Trigger workflow when a booking request is rejected by the host', + version: '1.0.0', + icon: CalComIcon, + + subBlocks: buildTriggerSubBlocks({ + triggerId: 'calcom_booking_rejected', + triggerOptions: calcomTriggerOptions, + setupInstructions: calcomSetupInstructions('rejected'), + extraFields: [calcomWebhookSecretField('calcom_booking_rejected')], + }), + + outputs: buildRejectedOutputs(), + + webhook: { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-Cal-Signature-256': 'sha256=...', + }, + }, +} diff --git a/apps/sim/triggers/calcom/booking_requested.ts b/apps/sim/triggers/calcom/booking_requested.ts new file mode 100644 index 000000000..9d589c8e1 --- /dev/null +++ b/apps/sim/triggers/calcom/booking_requested.ts @@ -0,0 +1,35 @@ +import { CalComIcon } from '@/components/icons' +import { buildTriggerSubBlocks } from '@/triggers' +import { + buildRequestedOutputs, + calcomSetupInstructions, + calcomTriggerOptions, + calcomWebhookSecretField, +} from '@/triggers/calcom/utils' +import type { TriggerConfig } from '@/triggers/types' + +export const calcomBookingRequestedTrigger: TriggerConfig = { + id: 'calcom_booking_requested', + name: 'CalCom Booking Requested', + provider: 'calcom', + description: 'Trigger workflow when a booking request is submitted (pending confirmation)', + version: '1.0.0', + icon: CalComIcon, + + subBlocks: buildTriggerSubBlocks({ + triggerId: 'calcom_booking_requested', + triggerOptions: calcomTriggerOptions, + setupInstructions: calcomSetupInstructions('requested'), + extraFields: [calcomWebhookSecretField('calcom_booking_requested')], + }), + + outputs: buildRequestedOutputs(), + + webhook: { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-Cal-Signature-256': 'sha256=...', + }, + }, +} diff --git a/apps/sim/triggers/calcom/index.ts b/apps/sim/triggers/calcom/index.ts index 1241393aa..d51ca0075 100644 --- a/apps/sim/triggers/calcom/index.ts +++ b/apps/sim/triggers/calcom/index.ts @@ -1,3 +1,9 @@ export { calcomBookingCancelledTrigger } from './booking_cancelled' export { calcomBookingCreatedTrigger } from './booking_created' +export { calcomBookingPaidTrigger } from './booking_paid' +export { calcomBookingRejectedTrigger } from './booking_rejected' +export { calcomBookingRequestedTrigger } from './booking_requested' export { calcomBookingRescheduledTrigger } from './booking_rescheduled' +export { calcomMeetingEndedTrigger } from './meeting_ended' +export { calcomRecordingReadyTrigger } from './recording_ready' +export { calcomWebhookTrigger } from './webhook' diff --git a/apps/sim/triggers/calcom/meeting_ended.ts b/apps/sim/triggers/calcom/meeting_ended.ts new file mode 100644 index 000000000..37aeacf0e --- /dev/null +++ b/apps/sim/triggers/calcom/meeting_ended.ts @@ -0,0 +1,35 @@ +import { CalComIcon } from '@/components/icons' +import { buildTriggerSubBlocks } from '@/triggers' +import { + buildMeetingEndedOutputs, + calcomSetupInstructions, + calcomTriggerOptions, + calcomWebhookSecretField, +} from '@/triggers/calcom/utils' +import type { TriggerConfig } from '@/triggers/types' + +export const calcomMeetingEndedTrigger: TriggerConfig = { + id: 'calcom_meeting_ended', + name: 'CalCom Meeting Ended', + provider: 'calcom', + description: 'Trigger workflow when a Cal.com meeting ends', + version: '1.0.0', + icon: CalComIcon, + + subBlocks: buildTriggerSubBlocks({ + triggerId: 'calcom_meeting_ended', + triggerOptions: calcomTriggerOptions, + setupInstructions: calcomSetupInstructions('meeting_ended'), + extraFields: [calcomWebhookSecretField('calcom_meeting_ended')], + }), + + outputs: buildMeetingEndedOutputs(), + + webhook: { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-Cal-Signature-256': 'sha256=...', + }, + }, +} diff --git a/apps/sim/triggers/calcom/recording_ready.ts b/apps/sim/triggers/calcom/recording_ready.ts new file mode 100644 index 000000000..aea43a754 --- /dev/null +++ b/apps/sim/triggers/calcom/recording_ready.ts @@ -0,0 +1,35 @@ +import { CalComIcon } from '@/components/icons' +import { buildTriggerSubBlocks } from '@/triggers' +import { + buildRecordingReadyOutputs, + calcomSetupInstructions, + calcomTriggerOptions, + calcomWebhookSecretField, +} from '@/triggers/calcom/utils' +import type { TriggerConfig } from '@/triggers/types' + +export const calcomRecordingReadyTrigger: TriggerConfig = { + id: 'calcom_recording_ready', + name: 'CalCom Recording Ready', + provider: 'calcom', + description: 'Trigger workflow when a meeting recording is ready for download', + version: '1.0.0', + icon: CalComIcon, + + subBlocks: buildTriggerSubBlocks({ + triggerId: 'calcom_recording_ready', + triggerOptions: calcomTriggerOptions, + setupInstructions: calcomSetupInstructions('recording_ready'), + extraFields: [calcomWebhookSecretField('calcom_recording_ready')], + }), + + outputs: buildRecordingReadyOutputs(), + + webhook: { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-Cal-Signature-256': 'sha256=...', + }, + }, +} diff --git a/apps/sim/triggers/calcom/utils.ts b/apps/sim/triggers/calcom/utils.ts index 55be4b497..35176dac7 100644 --- a/apps/sim/triggers/calcom/utils.ts +++ b/apps/sim/triggers/calcom/utils.ts @@ -41,6 +41,12 @@ export const calcomTriggerOptions = [ { label: 'Booking Created', id: 'calcom_booking_created' }, { label: 'Booking Cancelled', id: 'calcom_booking_cancelled' }, { label: 'Booking Rescheduled', id: 'calcom_booking_rescheduled' }, + { label: 'Booking Requested', id: 'calcom_booking_requested' }, + { label: 'Booking Rejected', id: 'calcom_booking_rejected' }, + { label: 'Booking Paid', id: 'calcom_booking_paid' }, + { label: 'Meeting Ended', id: 'calcom_meeting_ended' }, + { label: 'Recording Ready', id: 'calcom_recording_ready' }, + { label: 'Generic Webhook (All Events)', id: 'calcom_webhook' }, ] /** @@ -63,22 +69,47 @@ export function calcomWebhookSecretField(triggerId: string): SubBlockConfig { } } +/** + * Event type configuration for setup instructions + */ +type CalcomEventType = + | 'created' + | 'cancelled' + | 'rescheduled' + | 'requested' + | 'rejected' + | 'paid' + | 'meeting_ended' + | 'recording_ready' + | 'generic' + /** * Generates setup instructions HTML for CalCom triggers */ -export function calcomSetupInstructions( - eventType: 'created' | 'cancelled' | 'rescheduled' -): string { - const eventDescriptions = { +export function calcomSetupInstructions(eventType: CalcomEventType): string { + const eventDescriptions: Record = { created: 'This webhook triggers when a new booking is created.', cancelled: 'This webhook triggers when a booking is cancelled.', rescheduled: 'This webhook triggers when a booking is rescheduled.', + requested: + 'This webhook triggers when a booking request is submitted (for event types requiring confirmation).', + rejected: 'This webhook triggers when a booking request is rejected by the host.', + paid: 'This webhook triggers when payment is completed for a paid booking.', + meeting_ended: 'This webhook triggers when a meeting ends.', + recording_ready: 'This webhook triggers when a meeting recording is ready for download.', + generic: 'This webhook can receive any Cal.com event type you configure.', } - const eventNames = { + const eventNames: Record = { created: 'BOOKING_CREATED', cancelled: 'BOOKING_CANCELLED', rescheduled: 'BOOKING_RESCHEDULED', + requested: 'BOOKING_REQUESTED', + rejected: 'BOOKING_REJECTED', + paid: 'BOOKING_PAID', + meeting_ended: 'MEETING_ENDED', + recording_ready: 'RECORDING_READY', + generic: 'your desired event type(s)', } return [ @@ -311,3 +342,321 @@ export function buildRescheduledOutputs(): Record { }, } as any } + +/** + * Builds outputs for booking requested events (pending confirmation) + */ +export function buildRequestedOutputs(): Record { + return { + triggerEvent: { + type: 'string', + description: 'The webhook event type (BOOKING_REQUESTED)', + }, + createdAt: { + type: 'string', + description: 'When the webhook event was created (ISO 8601)', + }, + payload: { + title: { + type: 'string', + description: 'Booking title', + }, + description: { + type: 'string', + description: 'Booking description', + }, + eventTypeId: { + type: 'number', + description: 'Event type ID', + }, + startTime: { + type: 'string', + description: 'Requested start time (ISO 8601)', + }, + endTime: { + type: 'string', + description: 'Requested end time (ISO 8601)', + }, + uid: { + type: 'string', + description: 'Unique booking identifier', + }, + bookingId: { + type: 'number', + description: 'Numeric booking ID', + }, + status: { + type: 'string', + description: 'Booking status (pending)', + }, + location: { + type: 'string', + description: 'Meeting location or URL', + }, + organizer: ORGANIZER_OUTPUT, + attendees: ATTENDEES_TRIGGER_OUTPUT, + responses: { + type: 'object', + description: 'Form responses from booking request', + }, + metadata: { + type: 'object', + description: 'Custom metadata', + }, + }, + } as any +} + +/** + * Builds outputs for booking rejected events + */ +export function buildRejectedOutputs(): Record { + return { + triggerEvent: { + type: 'string', + description: 'The webhook event type (BOOKING_REJECTED)', + }, + createdAt: { + type: 'string', + description: 'When the webhook event was created (ISO 8601)', + }, + payload: { + title: { + type: 'string', + description: 'Booking title', + }, + description: { + type: 'string', + description: 'Booking description', + }, + eventTypeId: { + type: 'number', + description: 'Event type ID', + }, + startTime: { + type: 'string', + description: 'Requested start time (ISO 8601)', + }, + endTime: { + type: 'string', + description: 'Requested end time (ISO 8601)', + }, + uid: { + type: 'string', + description: 'Unique booking identifier', + }, + bookingId: { + type: 'number', + description: 'Numeric booking ID', + }, + status: { + type: 'string', + description: 'Booking status (rejected)', + }, + rejectionReason: { + type: 'string', + description: 'Reason for rejection provided by host', + }, + organizer: ORGANIZER_OUTPUT, + attendees: ATTENDEES_TRIGGER_OUTPUT, + metadata: { + type: 'object', + description: 'Custom metadata', + }, + }, + } as any +} + +/** + * Builds outputs for booking paid events + */ +export function buildPaidOutputs(): Record { + return { + triggerEvent: { + type: 'string', + description: 'The webhook event type (BOOKING_PAID)', + }, + createdAt: { + type: 'string', + description: 'When the webhook event was created (ISO 8601)', + }, + payload: { + title: { + type: 'string', + description: 'Booking title', + }, + description: { + type: 'string', + description: 'Booking description', + }, + eventTypeId: { + type: 'number', + description: 'Event type ID', + }, + startTime: { + type: 'string', + description: 'Booking start time (ISO 8601)', + }, + endTime: { + type: 'string', + description: 'Booking end time (ISO 8601)', + }, + uid: { + type: 'string', + description: 'Unique booking identifier', + }, + bookingId: { + type: 'number', + description: 'Numeric booking ID', + }, + status: { + type: 'string', + description: 'Booking status', + }, + location: { + type: 'string', + description: 'Meeting location or URL', + }, + payment: { + type: 'object', + description: 'Payment details', + properties: { + id: { type: 'string', description: 'Payment ID' }, + amount: { type: 'number', description: 'Payment amount' }, + currency: { type: 'string', description: 'Payment currency' }, + success: { type: 'boolean', description: 'Whether payment succeeded' }, + }, + }, + organizer: ORGANIZER_OUTPUT, + attendees: ATTENDEES_TRIGGER_OUTPUT, + metadata: { + type: 'object', + description: 'Custom metadata', + }, + }, + } as any +} + +/** + * Builds outputs for meeting ended events + */ +export function buildMeetingEndedOutputs(): Record { + return { + triggerEvent: { + type: 'string', + description: 'The webhook event type (MEETING_ENDED)', + }, + createdAt: { + type: 'string', + description: 'When the webhook event was created (ISO 8601)', + }, + payload: { + title: { + type: 'string', + description: 'Meeting title', + }, + eventTypeId: { + type: 'number', + description: 'Event type ID', + }, + startTime: { + type: 'string', + description: 'Meeting start time (ISO 8601)', + }, + endTime: { + type: 'string', + description: 'Meeting end time (ISO 8601)', + }, + uid: { + type: 'string', + description: 'Unique booking identifier', + }, + bookingId: { + type: 'number', + description: 'Numeric booking ID', + }, + duration: { + type: 'number', + description: 'Actual meeting duration in minutes', + }, + organizer: ORGANIZER_OUTPUT, + attendees: ATTENDEES_TRIGGER_OUTPUT, + videoCallData: { + type: 'object', + description: 'Video call details', + }, + }, + } as any +} + +/** + * Builds outputs for recording ready events + */ +export function buildRecordingReadyOutputs(): Record { + return { + triggerEvent: { + type: 'string', + description: 'The webhook event type (RECORDING_READY)', + }, + createdAt: { + type: 'string', + description: 'When the webhook event was created (ISO 8601)', + }, + payload: { + title: { + type: 'string', + description: 'Meeting title', + }, + eventTypeId: { + type: 'number', + description: 'Event type ID', + }, + startTime: { + type: 'string', + description: 'Meeting start time (ISO 8601)', + }, + endTime: { + type: 'string', + description: 'Meeting end time (ISO 8601)', + }, + uid: { + type: 'string', + description: 'Unique booking identifier', + }, + bookingId: { + type: 'number', + description: 'Numeric booking ID', + }, + recordingUrl: { + type: 'string', + description: 'URL to download the recording', + }, + transcription: { + type: 'string', + description: 'Meeting transcription text (if available)', + }, + organizer: ORGANIZER_OUTPUT, + attendees: ATTENDEES_TRIGGER_OUTPUT, + }, + } as any +} + +/** + * Builds outputs for generic webhook (any event type) + */ +export function buildGenericOutputs(): Record { + return { + triggerEvent: { + type: 'string', + description: 'The webhook event type (e.g., BOOKING_CREATED, MEETING_ENDED)', + }, + createdAt: { + type: 'string', + description: 'When the webhook event was created (ISO 8601)', + }, + payload: { + type: 'json', + description: 'Complete webhook payload (structure varies by event type)', + }, + } +} diff --git a/apps/sim/triggers/calcom/webhook.ts b/apps/sim/triggers/calcom/webhook.ts new file mode 100644 index 000000000..5bd6e733c --- /dev/null +++ b/apps/sim/triggers/calcom/webhook.ts @@ -0,0 +1,40 @@ +import { CalComIcon } from '@/components/icons' +import { buildTriggerSubBlocks } from '@/triggers' +import { + buildGenericOutputs, + calcomSetupInstructions, + calcomTriggerOptions, + calcomWebhookSecretField, +} from '@/triggers/calcom/utils' +import type { TriggerConfig } from '@/triggers/types' + +/** + * Generic Cal.com webhook trigger that accepts any event type. + * Use this when you need to handle events not covered by specific triggers, + * or when you want to receive multiple event types on the same webhook. + */ +export const calcomWebhookTrigger: TriggerConfig = { + id: 'calcom_webhook', + name: 'CalCom Webhook (All Events)', + provider: 'calcom', + description: 'Trigger workflow on any Cal.com webhook event (configure event types in Cal.com)', + version: '1.0.0', + icon: CalComIcon, + + subBlocks: buildTriggerSubBlocks({ + triggerId: 'calcom_webhook', + triggerOptions: calcomTriggerOptions, + setupInstructions: calcomSetupInstructions('generic'), + extraFields: [calcomWebhookSecretField('calcom_webhook')], + }), + + outputs: buildGenericOutputs(), + + webhook: { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-Cal-Signature-256': 'sha256=...', + }, + }, +} diff --git a/apps/sim/triggers/registry.ts b/apps/sim/triggers/registry.ts index 3a8baad68..09f72f3cf 100644 --- a/apps/sim/triggers/registry.ts +++ b/apps/sim/triggers/registry.ts @@ -2,7 +2,13 @@ import { airtableWebhookTrigger } from '@/triggers/airtable' import { calcomBookingCancelledTrigger, calcomBookingCreatedTrigger, + calcomBookingPaidTrigger, + calcomBookingRejectedTrigger, + calcomBookingRequestedTrigger, calcomBookingRescheduledTrigger, + calcomMeetingEndedTrigger, + calcomRecordingReadyTrigger, + calcomWebhookTrigger, } from '@/triggers/calcom' import { calendlyInviteeCanceledTrigger, @@ -128,6 +134,12 @@ export const TRIGGER_REGISTRY: TriggerRegistry = { calcom_booking_created: calcomBookingCreatedTrigger, calcom_booking_cancelled: calcomBookingCancelledTrigger, calcom_booking_rescheduled: calcomBookingRescheduledTrigger, + calcom_booking_requested: calcomBookingRequestedTrigger, + calcom_booking_rejected: calcomBookingRejectedTrigger, + calcom_booking_paid: calcomBookingPaidTrigger, + calcom_meeting_ended: calcomMeetingEndedTrigger, + calcom_recording_ready: calcomRecordingReadyTrigger, + calcom_webhook: calcomWebhookTrigger, generic_webhook: genericWebhookTrigger, github_webhook: githubWebhookTrigger, github_issue_opened: githubIssueOpenedTrigger,