@@ -166,6 +166,7 @@ async function handler(input: CancelBookingInput, dependencies?: Dependencies) {
166166 cancelSubsequentBookings,
167167 internalNote,
168168 skipCancellationReasonValidation = false ,
169+ skipCalendarSyncTaskCancellation = false ,
169170 } = bookingCancelInput . parse ( body ) ;
170171 const bookingToDelete = await getBookingToDelete ( id , uid ) ;
171172 const {
@@ -630,36 +631,46 @@ async function handler(input: CancelBookingInput, dependencies?: Dependencies) {
630631 allRemainingBookings
631632 ) ;
632633
633- try {
634- const bookingToDeleteEventTypeMetadataParsed = eventTypeMetaDataSchemaWithTypedApps . safeParse (
635- bookingToDelete . eventType ?. metadata || null
636- ) ;
637-
638- if ( ! bookingToDeleteEventTypeMetadataParsed . success ) {
639- log . error (
640- `Error parsing metadata` ,
641- safeStringify ( { error : bookingToDeleteEventTypeMetadataParsed ?. error } )
634+ // Skip calendar event deletion when cancellation comes from a calendar subscription webhook
635+ // to avoid infinite loops (Google/Office365 → Cal.com → Google/Office365 → ...)
636+ if ( ! skipCalendarSyncTaskCancellation ) {
637+ try {
638+ const bookingToDeleteEventTypeMetadataParsed = eventTypeMetaDataSchemaWithTypedApps . safeParse (
639+ bookingToDelete . eventType ?. metadata || null
642640 ) ;
643- throw new Error ( "Error parsing metadata" ) ;
644- }
645641
646- const bookingToDeleteEventTypeMetadata = bookingToDeleteEventTypeMetadataParsed . data ;
642+ if ( ! bookingToDeleteEventTypeMetadataParsed . success ) {
643+ log . error (
644+ `Error parsing metadata` ,
645+ safeStringify ( { error : bookingToDeleteEventTypeMetadataParsed ?. error } )
646+ ) ;
647+ throw new Error ( "Error parsing metadata" ) ;
648+ }
647649
648- const credentials = await getAllCredentialsIncludeServiceAccountKey ( bookingToDelete . user , {
649- ...bookingToDelete . eventType ,
650- metadata : bookingToDeleteEventTypeMetadata ,
651- } ) ;
650+ const bookingToDeleteEventTypeMetadata = bookingToDeleteEventTypeMetadataParsed . data ;
652651
653- const eventManager = new EventManager (
654- { ...bookingToDelete . user , credentials } ,
655- bookingToDeleteEventTypeMetadata ?. apps
656- ) ;
652+ const credentials = await getAllCredentialsIncludeServiceAccountKey ( bookingToDelete . user , {
653+ ...bookingToDelete . eventType ,
654+ metadata : bookingToDeleteEventTypeMetadata ,
655+ } ) ;
656+
657+ const eventManager = new EventManager (
658+ { ...bookingToDelete . user , credentials } ,
659+ bookingToDeleteEventTypeMetadata ?. apps
660+ ) ;
657661
658- await eventManager . cancelEvent ( evt , bookingToDelete . references , isBookingInRecurringSeries ) ;
662+ await eventManager . cancelEvent ( evt , bookingToDelete . references , isBookingInRecurringSeries ) ;
663+ } catch ( error ) {
664+ log . error ( `Error deleting integrations` , safeStringify ( { error } ) ) ;
665+ }
666+ }
659667
668+ // Always mark booking references as deleted for data consistency
669+ // (even when skipCalendarSyncTaskCancellation is true, since the external event is already deleted)
670+ try {
660671 await bookingReferenceRepository . updateManyByBookingId ( bookingToDelete . id , { deleted : true } ) ;
661672 } catch ( error ) {
662- log . error ( `Error deleting integrations ` , safeStringify ( { error } ) ) ;
673+ log . error ( `Error marking booking references as deleted ` , safeStringify ( { error } ) ) ;
663674 }
664675
665676 try {
0 commit comments