From 8de7b2d61ab04041952dcbd96f55c266b7f97671 Mon Sep 17 00:00:00 2001 From: ajayjha1 Date: Mon, 20 Apr 2026 19:49:32 +0530 Subject: [PATCH 1/2] fix: generate unique videoCallUrl per recurring booking occurrence For Cal Video (daily_video), each recurring booking occurrence now gets its own video room URL derived from its own uid, instead of all occurrences sharing the first booking's URL. --- apps/web/pages/api/book/recurring-event.test.ts | 3 --- .../features/bookings/lib/handleConfirmation.ts | 17 +++++++++++------ 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/apps/web/pages/api/book/recurring-event.test.ts b/apps/web/pages/api/book/recurring-event.test.ts index ee43b7f5237cfb..dc669d1cac4a47 100644 --- a/apps/web/pages/api/book/recurring-event.test.ts +++ b/apps/web/pages/api/book/recurring-event.test.ts @@ -202,7 +202,6 @@ describe("handleNewBooking", () => { organizer, location: "integrations:daily", subscriberUrl: "http://my-webhook.example.com", - //FIXME: All recurring bookings seem to have the same URL. https://github.com/calcom/cal.diy/issues/11955 videoCallUrl: `${WEBAPP_URL}/video/${createdBookings[0].uid}`, }); } @@ -549,7 +548,6 @@ describe("handleNewBooking", () => { organizer, location: "integrations:daily", subscriberUrl: "http://my-webhook.example.com", - //FIXME: File a bug - All recurring bookings seem to have the same URL. They should have same CalVideo URL which could mean that future recurring meetings would have already expired by the time they are needed. videoCallUrl: `${WEBAPP_URL}/video/${createdBookings[0].uid}`, }); } @@ -765,7 +763,6 @@ describe("handleNewBooking", () => { organizer, location: "integrations:daily", subscriberUrl: "http://my-webhook.example.com", - //FIXME: File a bug - All recurring bookings seem to have the same URL. They should have same CalVideo URL which could mean that future recurring meetings would have already expired by the time they are needed. videoCallUrl: `${WEBAPP_URL}/video/${createdBookings[0].uid}`, }); } diff --git a/packages/features/bookings/lib/handleConfirmation.ts b/packages/features/bookings/lib/handleConfirmation.ts index d9be4a9cb8ab26..fa13a7680e246c 100644 --- a/packages/features/bookings/lib/handleConfirmation.ts +++ b/packages/features/bookings/lib/handleConfirmation.ts @@ -6,7 +6,7 @@ import getWebhooks from "@calcom/features/webhooks/lib/getWebhooks"; import { scheduleTrigger } from "@calcom/features/webhooks/lib/scheduleTrigger"; import sendPayload from "@calcom/features/webhooks/lib/sendOrSchedulePayload"; import type { EventPayloadType, EventTypeInfo } from "@calcom/features/webhooks/lib/sendPayload"; -import { getVideoCallUrlFromCalEvent } from "@calcom/lib/CalEventParser"; +import { getVideoCallUrlFromCalEvent, getPublicVideoCallUrl, isDailyVideoCall } from "@calcom/lib/CalEventParser"; import { safeStringify } from "@calcom/lib/safeStringify"; import type { TraceContext } from "@calcom/lib/tracing"; import { distributedTracing } from "@calcom/lib/tracing/factory"; @@ -176,8 +176,13 @@ export async function handleConfirmation(args: { uid: booking.uid, })); - const updateBookingsPromise = unconfirmedRecurringBookings.map((recurringBooking) => - prisma.booking.update({ + const updateBookingsPromise = unconfirmedRecurringBookings.map((recurringBooking) => { + // For Cal Video (daily_video), each occurrence has its own room URL derived from its uid. + // For other providers, reuse the shared meetingUrl since they create one meeting per series. + const occurrenceVideoCallUrl = isDailyVideoCall(evt.videoCallData) + ? getPublicVideoCallUrl(recurringBooking.uid) + : meetingUrl; + return prisma.booking.update({ where: { id: recurringBooking.id, }, @@ -189,7 +194,7 @@ export async function handleConfirmation(args: { paid, metadata: { ...(typeof recurringBooking.metadata === "object" ? recurringBooking.metadata : {}), - videoCallUrl: meetingUrl, + videoCallUrl: occurrenceVideoCallUrl, }, }, select: { @@ -234,8 +239,8 @@ export async function handleConfirmation(args: { customInputs: true, id: true, }, - }) - ); + }); + }); const updatedBookingsResult = await Promise.all(updateBookingsPromise); updatedBookings = updatedBookings.concat(updatedBookingsResult); From 4e4796bd99c0269781e1c1fc5ac7821e4b6ad882 Mon Sep 17 00:00:00 2001 From: ajayjha1 Date: Mon, 20 Apr 2026 21:49:40 +0530 Subject: [PATCH 2/2] fix: clarify series-level webhook uses first occurrence videoCallUrl --- packages/features/bookings/lib/handleConfirmation.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/features/bookings/lib/handleConfirmation.ts b/packages/features/bookings/lib/handleConfirmation.ts index fa13a7680e246c..053531446974d9 100644 --- a/packages/features/bookings/lib/handleConfirmation.ts +++ b/packages/features/bookings/lib/handleConfirmation.ts @@ -412,6 +412,9 @@ export async function handleConfirmation(args: { eventTypeId: eventType?.id, status: "ACCEPTED", smsReminderNumber: booking.smsReminderNumber || undefined, + // This is a single series-level webhook fired once on confirmation. + // It uses the first occurrence's URL. Webhook consumers needing per-occurrence + // URLs should query each booking's metadata.videoCallUrl from the database. metadata: meetingUrl ? { videoCallUrl: meetingUrl } : {}, ...(platformClientParams ? platformClientParams : {}), };