fix: revert assignmentReason breaking change in webhook payloads (#27891)
* fix: revert assignmentReason breaking change in webhook payloads
Remove the new { category, details } format from EventPayloadType to
maintain backward compatibility for webhook consumers. The new format
is stripped at all webhook payload construction sites by destructuring
assignmentReason out of CalendarEvent before spreading into the payload.
A sanitizeAssignmentReasonForWebhook function provides an additional
safety net in sendPayload itself. Emails and booking single view
continue to use the new format via CalendarEvent.
Co-Authored-By: ali@cal.com <alishahbaz7@gmail.com>
* fix: strip assignmentReason from handlePaymentSuccess webhook payload
Co-Authored-By: ali@cal.com <alishahbaz7@gmail.com>
* fix: strip assignmentReason from triggerWebhooks and handleSeats webhook payloads
Co-Authored-By: ali@cal.com <alishahbaz7@gmail.com>
* refactor: use zod safeParse instead of type assertion in sanitizeAssignmentReasonForWebhook
Co-Authored-By: ali@cal.com <alishahbaz7@gmail.com>
---------
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
ali@cal.com
Devin AI
parent
21d28c9747
commit
3a7122d613
@@ -54,8 +54,9 @@ export const triggerRecordingReadyWebhook = async ({
|
||||
})
|
||||
);
|
||||
|
||||
const { assignmentReason: _emailAssignmentReason, ...evtWithoutAssignmentReason } = evt;
|
||||
const payload: EventPayloadType = {
|
||||
...evt,
|
||||
...evtWithoutAssignmentReason,
|
||||
downloadLink,
|
||||
};
|
||||
|
||||
@@ -94,8 +95,9 @@ export const triggerTranscriptionGeneratedWebhook = async ({
|
||||
})
|
||||
);
|
||||
|
||||
const { assignmentReason: _emailAssignmentReason2, ...evtWithoutAssignmentReason2 } = evt;
|
||||
const payload: EventPayloadType = {
|
||||
...evt,
|
||||
...evtWithoutAssignmentReason2,
|
||||
downloadLinks,
|
||||
};
|
||||
|
||||
|
||||
@@ -160,8 +160,9 @@ export async function handlePaymentSuccess(params: {
|
||||
length: booking.eventType?.length,
|
||||
};
|
||||
|
||||
const { assignmentReason: _emailAssignmentReason, ...evtWithoutAssignmentReason } = evt;
|
||||
const payload: EventPayloadType = {
|
||||
...evt,
|
||||
...evtWithoutAssignmentReason,
|
||||
...eventTypeInfo,
|
||||
bookingId,
|
||||
eventTypeId: booking.eventType?.id,
|
||||
|
||||
@@ -30,8 +30,9 @@ export const getWebhookPayloadForBooking = ({
|
||||
length: booking.eventType?.length,
|
||||
};
|
||||
|
||||
const { assignmentReason: _emailAssignmentReason, ...evtWithoutAssignmentReason } = evt;
|
||||
const payload: EventPayloadType = {
|
||||
...evt,
|
||||
...evtWithoutAssignmentReason,
|
||||
...eventTypeInfo,
|
||||
bookingId: booking.id,
|
||||
};
|
||||
|
||||
@@ -13,11 +13,11 @@ import {
|
||||
import type { ActionSource } from "@calcom/features/booking-audit/lib/types/actionSource";
|
||||
import { BookingReferenceRepository } from "@calcom/features/bookingReference/repositories/BookingReferenceRepository";
|
||||
import { getBookingEventHandlerService } from "@calcom/features/bookings/di/BookingEventHandlerService.container";
|
||||
import { getFeaturesRepository } from "@calcom/features/di/containers/FeaturesRepository";
|
||||
import EventManager from "@calcom/features/bookings/lib/EventManager";
|
||||
import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses";
|
||||
import { processNoShowFeeOnCancellation } from "@calcom/features/bookings/lib/payment/processNoShowFeeOnCancellation";
|
||||
import { processPaymentRefund } from "@calcom/features/bookings/lib/payment/processPaymentRefund";
|
||||
import { getFeaturesRepository } from "@calcom/features/di/containers/FeaturesRepository";
|
||||
import { CreditService } from "@calcom/features/ee/billing/credit-service";
|
||||
import { getBookerBaseUrl } from "@calcom/features/ee/organizations/lib/getBookerUrlServer";
|
||||
import { getAllWorkflowsFromEventType } from "@calcom/features/ee/workflows/lib/getAllWorkflowsFromEventType";
|
||||
@@ -429,9 +429,10 @@ async function handler(input: CancelBookingInput, dependencies?: Dependencies) {
|
||||
isPlatformManagedUserBooking: bookingToDelete.user.isPlatformManaged,
|
||||
} satisfies HandleCancelBookingResponse;
|
||||
|
||||
const { assignmentReason: _emailAssignmentReason, ...evtWithoutAssignmentReason } = evt;
|
||||
const promises = webhooks.map((webhook) =>
|
||||
sendPayload(webhook.secret, eventTrigger, new Date().toISOString(), webhook, {
|
||||
...evt,
|
||||
...evtWithoutAssignmentReason,
|
||||
...eventTypeInfo,
|
||||
status: "CANCELLED",
|
||||
smsReminderNumber: bookingToDelete.smsReminderNumber || undefined,
|
||||
|
||||
@@ -4,9 +4,10 @@ import { sendScheduledEmailsAndSMS } from "@calcom/emails/email-manager";
|
||||
import type { Actor } from "@calcom/features/booking-audit/lib/dto/types";
|
||||
import type { ActionSource } from "@calcom/features/booking-audit/lib/types/actionSource";
|
||||
import { getBookingEventHandlerService } from "@calcom/features/bookings/di/BookingEventHandlerService.container";
|
||||
import { getFeaturesRepository } from "@calcom/features/di/containers/FeaturesRepository";
|
||||
import type { EventManagerUser } from "@calcom/features/bookings/lib/EventManager";
|
||||
import EventManager, { placeholderCreatedEvent } from "@calcom/features/bookings/lib/EventManager";
|
||||
import { getFeaturesRepository } from "@calcom/features/di/containers/FeaturesRepository";
|
||||
import type { ISimpleLogger } from "@calcom/features/di/shared/services/logger.service";
|
||||
import { CreditService } from "@calcom/features/ee/billing/credit-service";
|
||||
import { getBookerBaseUrl } from "@calcom/features/ee/organizations/lib/getBookerUrlServer";
|
||||
import {
|
||||
@@ -34,10 +35,8 @@ import type { PlatformClientParams } from "@calcom/prisma/zod-utils";
|
||||
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
|
||||
import type { AdditionalInformation, CalendarEvent } from "@calcom/types/Calendar";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
|
||||
import { getCalEventResponses } from "./getCalEventResponses";
|
||||
import { scheduleNoShowTriggers } from "./handleNewBooking/scheduleNoShowTriggers";
|
||||
import type { ISimpleLogger } from "@calcom/features/di/shared/services/logger.service";
|
||||
|
||||
async function fireBookingAcceptedEvent({
|
||||
actor,
|
||||
@@ -570,8 +569,9 @@ export async function handleConfirmation(args: {
|
||||
length: eventType?.length,
|
||||
};
|
||||
|
||||
const { assignmentReason: _emailAssignmentReason, ...evtWithoutAssignmentReason } = evt;
|
||||
const payload: EventPayloadType = {
|
||||
...evt,
|
||||
...evtWithoutAssignmentReason,
|
||||
...eventTypeInfo,
|
||||
bookingId,
|
||||
eventTypeId: eventType?.id,
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { getCalendar } from "@calcom/app-store/_utils/getCalendar";
|
||||
import { getAllDelegationCredentialsForUserIncludeServiceAccountKey } from "@calcom/app-store/delegationCredential";
|
||||
import { getDelegationCredentialOrFindRegularCredential } from "@calcom/app-store/delegationCredential";
|
||||
import {
|
||||
getAllDelegationCredentialsForUserIncludeServiceAccountKey,
|
||||
getDelegationCredentialOrFindRegularCredential,
|
||||
} from "@calcom/app-store/delegationCredential";
|
||||
import { sendCancelledSeatEmailsAndSMS } from "@calcom/emails/email-manager";
|
||||
import { updateMeeting } from "@calcom/features/conferencing/lib/videoClient";
|
||||
import { WorkflowRepository } from "@calcom/features/ee/workflows/repositories/WorkflowRepository";
|
||||
@@ -14,10 +16,9 @@ import { safeStringify } from "@calcom/lib/safeStringify";
|
||||
import { getTranslation } from "@calcom/lib/server/i18n";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { WebhookTriggerEvents } from "@calcom/prisma/enums";
|
||||
import { bookingCancelAttendeeSeatSchema } from "@calcom/prisma/zod-utils";
|
||||
import type { EventTypeMetadata } from "@calcom/prisma/zod-utils";
|
||||
import { bookingCancelAttendeeSeatSchema } from "@calcom/prisma/zod-utils";
|
||||
import type { CalendarEvent } from "@calcom/types/Calendar";
|
||||
|
||||
import type { BookingToDelete } from "../../handleCancelBooking";
|
||||
|
||||
async function cancelAttendeeSeat(
|
||||
@@ -159,8 +160,9 @@ async function cancelAttendeeSeat(
|
||||
]
|
||||
: [];
|
||||
|
||||
const { assignmentReason: _emailAssignmentReason, ...evtWithoutAssignmentReason } = evt;
|
||||
const payload: EventPayloadType = {
|
||||
...evt,
|
||||
...evtWithoutAssignmentReason,
|
||||
...eventTypeInfo,
|
||||
status: "CANCELLED",
|
||||
smsReminderNumber: bookingToDelete.smsReminderNumber || undefined,
|
||||
|
||||
@@ -284,8 +284,9 @@ const handleSeats = async (
|
||||
loggerWithEventDetails.error("Error while scheduling workflow reminders", JSON.stringify({ error }));
|
||||
}
|
||||
|
||||
const { assignmentReason: _emailAssignmentReason, ...evtWithoutAssignmentReason } = evt;
|
||||
const webhookData: EventPayloadType = {
|
||||
...evt,
|
||||
...evtWithoutAssignmentReason,
|
||||
...eventTypeInfo,
|
||||
uid: resultBooking?.uid || uid,
|
||||
bookingId: seatedBooking?.id,
|
||||
|
||||
@@ -1,11 +1,4 @@
|
||||
import short, { uuid } from "short-uuid";
|
||||
import { v5 as uuidv5 } from "uuid";
|
||||
import { getAuditActionSource } from "../handleNewBooking/getAuditActionSource";
|
||||
import {
|
||||
buildBookingCreatedAuditData,
|
||||
buildBookingRescheduledAuditData,
|
||||
} from "../handleNewBooking/buildBookingEventAuditData";
|
||||
import type { ActionSource } from "@calcom/features/booking-audit/lib/types/actionSource";
|
||||
import process from "node:process";
|
||||
import processExternalId from "@calcom/app-store/_utils/calendars/processExternalId";
|
||||
import { getPaymentAppData } from "@calcom/app-store/_utils/payments/getPaymentAppData";
|
||||
import {
|
||||
@@ -20,33 +13,33 @@ import {
|
||||
} from "@calcom/app-store/locations";
|
||||
import { getAppFromSlug } from "@calcom/app-store/utils";
|
||||
import {
|
||||
eventTypeMetaDataSchemaWithTypedApps,
|
||||
eventTypeAppMetadataOptionalSchema,
|
||||
eventTypeMetaDataSchemaWithTypedApps,
|
||||
} from "@calcom/app-store/zod-utils";
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import { scheduleMandatoryReminder } from "@calcom/ee/workflows/lib/reminders/scheduleMandatoryReminder";
|
||||
import getICalUID from "@calcom/emails/lib/getICalUID";
|
||||
import { CalendarEventBuilder } from "@calcom/features/CalendarEventBuilder";
|
||||
import { verifyCodeUnAuthenticated } from "@calcom/features/auth/lib/verifyCodeUnAuthenticated";
|
||||
import { getAssignmentReasonCategory } from "@calcom/features/bookings/lib/getAssignmentReasonCategory";
|
||||
import EventManager, { placeholderCreatedEvent } from "@calcom/features/bookings/lib/EventManager";
|
||||
import type { BookingDataSchemaGetter } from "@calcom/features/bookings/lib/dto/types";
|
||||
import type { ActionSource } from "@calcom/features/booking-audit/lib/types/actionSource";
|
||||
import type {
|
||||
CreateRegularBookingData,
|
||||
CreateBookingMeta,
|
||||
BookingDataSchemaGetter,
|
||||
BookingHandlerInput,
|
||||
CreateBookingMeta,
|
||||
CreateRegularBookingData,
|
||||
} from "@calcom/features/bookings/lib/dto/types";
|
||||
import EventManager, { placeholderCreatedEvent } from "@calcom/features/bookings/lib/EventManager";
|
||||
import { getAssignmentReasonCategory } from "@calcom/features/bookings/lib/getAssignmentReasonCategory";
|
||||
import type { CheckBookingAndDurationLimitsService } from "@calcom/features/bookings/lib/handleNewBooking/checkBookingAndDurationLimits";
|
||||
import { handlePayment } from "@calcom/features/bookings/lib/handlePayment";
|
||||
import { handleWebhookTrigger } from "@calcom/features/bookings/lib/handleWebhookTrigger";
|
||||
import { isEventTypeLoggingEnabled } from "@calcom/features/bookings/lib/isEventTypeLoggingEnabled";
|
||||
import { BookingEventHandlerService } from "@calcom/features/bookings/lib/onBookingEvents/BookingEventHandlerService";
|
||||
import type { BookingEventHandlerService } from "@calcom/features/bookings/lib/onBookingEvents/BookingEventHandlerService";
|
||||
import type { BookingRescheduledPayload } from "@calcom/features/bookings/lib/onBookingEvents/types.d";
|
||||
import { BookingEmailAndSmsTasker } from "@calcom/features/bookings/lib/tasker/BookingEmailAndSmsTasker";
|
||||
import type { BookingEmailAndSmsTasker } from "@calcom/features/bookings/lib/tasker/BookingEmailAndSmsTasker";
|
||||
import { CalendarEventBuilder } from "@calcom/features/CalendarEventBuilder";
|
||||
import { getSpamCheckService } from "@calcom/features/di/watchlist/containers/SpamCheckService.container";
|
||||
import { CreditService } from "@calcom/features/ee/billing/credit-service";
|
||||
import { getBookerBaseUrl } from "@calcom/features/ee/organizations/lib/getBookerUrlServer";
|
||||
import { getRoutingTraceService } from "@calcom/features/routing-trace/di/RoutingTraceService.container";
|
||||
import AssignmentReasonRecorder from "@calcom/features/ee/round-robin/assignmentReason/AssignmentReasonRecorder";
|
||||
import { BookingLocationService } from "@calcom/features/ee/round-robin/lib/bookingLocationService";
|
||||
import { getAllWorkflowsFromEventType } from "@calcom/features/ee/workflows/lib/getAllWorkflowsFromEventType";
|
||||
@@ -54,24 +47,25 @@ import { WorkflowService } from "@calcom/features/ee/workflows/lib/service/Workf
|
||||
import { WorkflowRepository } from "@calcom/features/ee/workflows/repositories/WorkflowRepository";
|
||||
import { getUsernameList } from "@calcom/features/eventtypes/lib/defaultEvents";
|
||||
import { getEventName, updateHostInEventName } from "@calcom/features/eventtypes/lib/eventNaming";
|
||||
import { FeaturesRepository } from "@calcom/features/flags/features.repository";
|
||||
import type { FeaturesRepository } from "@calcom/features/flags/features.repository";
|
||||
import { getFullName } from "@calcom/features/form-builder/utils";
|
||||
import type { HashedLinkService } from "@calcom/features/hashedLink/lib/service/HashedLinkService";
|
||||
import { ProfileRepository } from "@calcom/features/profile/repositories/ProfileRepository";
|
||||
import { getRoutingTraceService } from "@calcom/features/routing-trace/di/RoutingTraceService.container";
|
||||
import { handleAnalyticsEvents } from "@calcom/features/tasker/tasks/analytics/handleAnalyticsEvents";
|
||||
import type { UserRepository } from "@calcom/features/users/repositories/UserRepository";
|
||||
import { UsersRepository } from "@calcom/features/users/users.repository";
|
||||
import type { GetSubscriberOptions } from "@calcom/features/webhooks/lib/getWebhooks";
|
||||
import getWebhooks from "@calcom/features/webhooks/lib/getWebhooks";
|
||||
import {
|
||||
deleteWebhookScheduledTriggers,
|
||||
cancelNoShowTasksForBooking,
|
||||
deleteWebhookScheduledTriggers,
|
||||
scheduleTrigger,
|
||||
} from "@calcom/features/webhooks/lib/scheduleTrigger";
|
||||
import type { EventPayloadType, EventTypeInfo } from "@calcom/features/webhooks/lib/sendPayload";
|
||||
import { getVideoCallUrlFromCalEvent } from "@calcom/lib/CalEventParser";
|
||||
import { groupHostsByGroupId } from "@calcom/lib/bookings/hostGroupUtils";
|
||||
import { shouldIgnoreContactOwner } from "@calcom/lib/bookings/routing/utils";
|
||||
import { getVideoCallUrlFromCalEvent } from "@calcom/lib/CalEventParser";
|
||||
import { DEFAULT_GROUP_ID, ENABLE_ASYNC_TASKER } from "@calcom/lib/constants";
|
||||
import { ErrorCode } from "@calcom/lib/errorCodes";
|
||||
import { ErrorWithCode } from "@calcom/lib/errors";
|
||||
@@ -87,36 +81,43 @@ import { getTranslation } from "@calcom/lib/server/i18n";
|
||||
import { getTimeFormatStringFromUserTimeFormat } from "@calcom/lib/timeFormat";
|
||||
import { distributedTracing } from "@calcom/lib/tracing/factory";
|
||||
import type { PrismaClient } from "@calcom/prisma";
|
||||
import type { DestinationCalendar, Prisma, User, AssignmentReasonEnum } from "@calcom/prisma/client";
|
||||
import type { AssignmentReasonEnum, DestinationCalendar, Prisma, User } from "@calcom/prisma/client";
|
||||
import {
|
||||
BookingStatus,
|
||||
CreationSource,
|
||||
SchedulingType,
|
||||
WebhookTriggerEvents,
|
||||
WorkflowTriggerEvents,
|
||||
CreationSource,
|
||||
} from "@calcom/prisma/enums";
|
||||
import { userMetadata as userMetadataSchema } from "@calcom/prisma/zod-utils";
|
||||
import type {
|
||||
AdditionalInformation,
|
||||
AppsStatus,
|
||||
CalendarEvent,
|
||||
CalEventResponses,
|
||||
CalendarEvent,
|
||||
} from "@calcom/types/Calendar";
|
||||
import type { CredentialForCalendarService } from "@calcom/types/Credential";
|
||||
import type { EventResult, PartialReference } from "@calcom/types/EventManager";
|
||||
|
||||
import short, { uuid } from "short-uuid";
|
||||
import { v5 as uuidv5 } from "uuid";
|
||||
import type { BookingRepository } from "../../repositories/BookingRepository";
|
||||
import { BookingActionMap, BookingEmailSmsHandler, type BookingActionType } from "../BookingEmailSmsHandler";
|
||||
import { BookingActionMap, type BookingActionType, BookingEmailSmsHandler } from "../BookingEmailSmsHandler";
|
||||
import { getAllCredentialsIncludeServiceAccountKey } from "../getAllCredentialsForUsersOnEvent/getAllCredentials";
|
||||
import { refreshCredentials } from "../getAllCredentialsForUsersOnEvent/refreshCredentials";
|
||||
import getBookingDataSchema from "../getBookingDataSchema";
|
||||
import { LuckyUserService } from "../getLuckyUser";
|
||||
import type { LuckyUserService } from "../getLuckyUser";
|
||||
import { addVideoCallDataToEvent } from "../handleNewBooking/addVideoCallDataToEvent";
|
||||
import {
|
||||
buildBookingCreatedAuditData,
|
||||
buildBookingRescheduledAuditData,
|
||||
} from "../handleNewBooking/buildBookingEventAuditData";
|
||||
import { checkActiveBookingsLimitForBooker } from "../handleNewBooking/checkActiveBookingsLimitForBooker";
|
||||
import { checkIfBookerEmailIsBlocked } from "../handleNewBooking/checkIfBookerEmailIsBlocked";
|
||||
import { createBooking } from "../handleNewBooking/createBooking";
|
||||
import type { Booking } from "../handleNewBooking/createBooking";
|
||||
import { createBooking } from "../handleNewBooking/createBooking";
|
||||
import { ensureAvailableUsers } from "../handleNewBooking/ensureAvailableUsers";
|
||||
import { getAuditActionSource } from "../handleNewBooking/getAuditActionSource";
|
||||
import { getBookingAuditActorForNewBooking } from "../handleNewBooking/getBookingAuditActorForNewBooking";
|
||||
import { getBookingData } from "../handleNewBooking/getBookingData";
|
||||
import { getCustomInputsResponses } from "../handleNewBooking/getCustomInputsResponses";
|
||||
import { getEventType } from "../handleNewBooking/getEventType";
|
||||
@@ -127,15 +128,14 @@ import { getSeatedBooking } from "../handleNewBooking/getSeatedBooking";
|
||||
import { getVideoCallDetails } from "../handleNewBooking/getVideoCallDetails";
|
||||
import { handleAppsStatus } from "../handleNewBooking/handleAppsStatus";
|
||||
import { loadAndValidateUsers } from "../handleNewBooking/loadAndValidateUsers";
|
||||
import { getOriginalRescheduledBooking } from "../handleNewBooking/originalRescheduledBookingUtils";
|
||||
import type { BookingType } from "../handleNewBooking/originalRescheduledBookingUtils";
|
||||
import { getOriginalRescheduledBooking } from "../handleNewBooking/originalRescheduledBookingUtils";
|
||||
import { scheduleNoShowTriggers } from "../handleNewBooking/scheduleNoShowTriggers";
|
||||
import type { IEventTypePaymentCredentialType, Invitee, IsFixedAwareUser } from "../handleNewBooking/types";
|
||||
import { validateBookingTimeIsNotOutOfBounds } from "../handleNewBooking/validateBookingTimeIsNotOutOfBounds";
|
||||
import { validateEventLength } from "../handleNewBooking/validateEventLength";
|
||||
import handleSeats from "../handleSeats/handleSeats";
|
||||
import type { IBookingService } from "../interfaces/IBookingService";
|
||||
import { getBookingAuditActorForNewBooking } from "../handleNewBooking/getBookingAuditActorForNewBooking";
|
||||
import { isWithinMinimumRescheduleNotice } from "../reschedule/isWithinMinimumRescheduleNotice";
|
||||
|
||||
const translator = short();
|
||||
@@ -1305,7 +1305,7 @@ async function handler(
|
||||
const isManagedEventType = !!eventType.parentId;
|
||||
|
||||
// Track credential ID for per-host locations
|
||||
let perHostCredentialId: number | undefined = undefined;
|
||||
let perHostCredentialId: number | undefined;
|
||||
|
||||
// Handle per-host custom locations for round-robin events
|
||||
if (
|
||||
@@ -2498,8 +2498,9 @@ async function handler(
|
||||
|
||||
const webhookLocation = metadata?.videoCallUrl || evt.location;
|
||||
|
||||
const { assignmentReason: _emailAssignmentReason, ...evtWithoutAssignmentReason } = evt;
|
||||
const webhookData: EventPayloadType = {
|
||||
...evt,
|
||||
...evtWithoutAssignmentReason,
|
||||
...eventTypeInfo,
|
||||
bookingId: booking?.id,
|
||||
rescheduleId: originalRescheduledBooking?.id || undefined,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { WebhookVersion } from "./interface/IWebhookRepository";
|
||||
import sendPayload from "./sendPayload";
|
||||
import type { EventPayloadType } from "./sendPayload";
|
||||
import sendPayload, { sanitizeAssignmentReasonForWebhook } from "./sendPayload";
|
||||
|
||||
describe("sendPayload", () => {
|
||||
const mockFetch = vi.fn();
|
||||
@@ -108,4 +108,138 @@ describe("sendPayload", () => {
|
||||
expect(options.headers["X-Cal-Webhook-Version"]).toBe("2021-10-20");
|
||||
});
|
||||
});
|
||||
|
||||
describe("sanitizeAssignmentReasonForWebhook", () => {
|
||||
const basePayload = {
|
||||
title: "Test Booking",
|
||||
startTime: "2024-01-01T10:00:00Z",
|
||||
endTime: "2024-01-01T11:00:00Z",
|
||||
organizer: {
|
||||
email: "organizer@example.com",
|
||||
name: "Organizer",
|
||||
timeZone: "UTC",
|
||||
language: { locale: "en" },
|
||||
},
|
||||
attendees: [],
|
||||
type: "test-event",
|
||||
} as unknown as EventPayloadType;
|
||||
|
||||
it("should preserve string assignmentReason", () => {
|
||||
const data = { ...basePayload, assignmentReason: "Salesforce contact owner: user@example.com" };
|
||||
const result = sanitizeAssignmentReasonForWebhook(data);
|
||||
expect(result.assignmentReason).toBe("Salesforce contact owner: user@example.com");
|
||||
});
|
||||
|
||||
it("should preserve array assignmentReason with reasonEnum and reasonString", () => {
|
||||
const reasons = [{ reasonEnum: "ROUTED", reasonString: "Language: English" }];
|
||||
const data = { ...basePayload, assignmentReason: reasons };
|
||||
const result = sanitizeAssignmentReasonForWebhook(data);
|
||||
expect(result.assignmentReason).toEqual(reasons);
|
||||
});
|
||||
|
||||
it("should preserve null assignmentReason", () => {
|
||||
const data = { ...basePayload, assignmentReason: null };
|
||||
const result = sanitizeAssignmentReasonForWebhook(data);
|
||||
expect(result.assignmentReason).toBeNull();
|
||||
});
|
||||
|
||||
it("should preserve undefined assignmentReason", () => {
|
||||
const data = { ...basePayload };
|
||||
const result = sanitizeAssignmentReasonForWebhook(data);
|
||||
expect(result.assignmentReason).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should strip CalendarEvent email format { category, details }", () => {
|
||||
const data = {
|
||||
...basePayload,
|
||||
assignmentReason: { category: "routed", details: "Language: English" },
|
||||
} as unknown as EventPayloadType;
|
||||
const result = sanitizeAssignmentReasonForWebhook(data);
|
||||
expect(result.assignmentReason).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should strip CalendarEvent email format with null details", () => {
|
||||
const data = {
|
||||
...basePayload,
|
||||
assignmentReason: { category: "reassigned", details: null },
|
||||
} as unknown as EventPayloadType;
|
||||
const result = sanitizeAssignmentReasonForWebhook(data);
|
||||
expect(result.assignmentReason).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should not strip other fields when stripping assignmentReason", () => {
|
||||
const data = {
|
||||
...basePayload,
|
||||
bookingId: 123,
|
||||
status: "ACCEPTED",
|
||||
assignmentReason: { category: "routed", details: "test" },
|
||||
} as unknown as EventPayloadType;
|
||||
const result = sanitizeAssignmentReasonForWebhook(data);
|
||||
expect(result.assignmentReason).toBeUndefined();
|
||||
expect(result.bookingId).toBe(123);
|
||||
expect(result.status).toBe("ACCEPTED");
|
||||
expect(result.title).toBe("Test Booking");
|
||||
});
|
||||
});
|
||||
|
||||
describe("sendPayload strips CalendarEvent assignmentReason format", () => {
|
||||
it("should not include { category, details } format in webhook payload", async () => {
|
||||
const webhook = {
|
||||
subscriberUrl: "https://example.com/webhook",
|
||||
appId: null,
|
||||
payloadTemplate: null,
|
||||
version: WebhookVersion.V_2021_10_20,
|
||||
};
|
||||
|
||||
await sendPayload("test-secret", "BOOKING_CREATED", new Date().toISOString(), webhook, {
|
||||
title: "Test Booking",
|
||||
startTime: "2024-01-01T10:00:00Z",
|
||||
endTime: "2024-01-01T11:00:00Z",
|
||||
organizer: {
|
||||
email: "organizer@example.com",
|
||||
name: "Organizer",
|
||||
timeZone: "UTC",
|
||||
language: { locale: "en" },
|
||||
},
|
||||
attendees: [],
|
||||
type: "test-event",
|
||||
description: "",
|
||||
assignmentReason: { category: "routed", details: "Language: English" },
|
||||
} as unknown as Parameters<typeof sendPayload>[4]);
|
||||
|
||||
const [, options] = mockFetch.mock.calls[0];
|
||||
const body = JSON.parse(options.body);
|
||||
expect(body.payload.assignmentReason).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should preserve array assignmentReason format in webhook payload", async () => {
|
||||
const webhook = {
|
||||
subscriberUrl: "https://example.com/webhook",
|
||||
appId: null,
|
||||
payloadTemplate: null,
|
||||
version: WebhookVersion.V_2021_10_20,
|
||||
};
|
||||
|
||||
const reasons = [{ reasonEnum: "ROUTED", reasonString: "Language: English" }];
|
||||
await sendPayload("test-secret", "BOOKING_CREATED", new Date().toISOString(), webhook, {
|
||||
title: "Test Booking",
|
||||
startTime: "2024-01-01T10:00:00Z",
|
||||
endTime: "2024-01-01T11:00:00Z",
|
||||
organizer: {
|
||||
email: "organizer@example.com",
|
||||
name: "Organizer",
|
||||
timeZone: "UTC",
|
||||
language: { locale: "en" },
|
||||
},
|
||||
attendees: [],
|
||||
type: "test-event",
|
||||
description: "",
|
||||
assignmentReason: reasons,
|
||||
} as unknown as Parameters<typeof sendPayload>[4]);
|
||||
|
||||
const [, options] = mockFetch.mock.calls[0];
|
||||
const body = JSON.parse(options.body);
|
||||
expect(body.payload.assignmentReason).toEqual(reasons);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import { createHmac } from "node:crypto";
|
||||
import { compile } from "handlebars";
|
||||
|
||||
import type { TGetTranscriptAccessLink } from "@calcom/app-store/dailyvideo/zod";
|
||||
import { getHumanReadableLocationValue } from "@calcom/app-store/locations";
|
||||
import type { WebhookSubscriber, PaymentData } from "@calcom/features/webhooks/lib/dto/types";
|
||||
import { DelegationCredentialErrorPayloadType } from "@calcom/features/webhooks/lib/dto/types";
|
||||
import type {
|
||||
DelegationCredentialErrorPayloadType,
|
||||
PaymentData,
|
||||
WebhookSubscriber,
|
||||
} from "@calcom/features/webhooks/lib/dto/types";
|
||||
import { getUTCOffsetByTimezone } from "@calcom/lib/dayjs";
|
||||
import type { CalendarEvent, Person } from "@calcom/types/Calendar";
|
||||
import { compile } from "handlebars";
|
||||
import { z } from "zod";
|
||||
|
||||
// Minimal webhook shape for sending payloads (subset of WebhookSubscriber)
|
||||
type WebhookForPayload = Pick<WebhookSubscriber, "subscriberUrl" | "appId" | "payloadTemplate" | "version">;
|
||||
@@ -97,11 +100,7 @@ export type EventPayloadType = Omit<CalendarEvent, "assignmentReason"> &
|
||||
cancelledBy?: string;
|
||||
paymentData?: PaymentData;
|
||||
requestReschedule?: boolean;
|
||||
assignmentReason?:
|
||||
| string
|
||||
| { reasonEnum: string; reasonString: string }[]
|
||||
| { category: string; details?: string | null }
|
||||
| null;
|
||||
assignmentReason?: string | { reasonEnum: string; reasonString: string }[] | null;
|
||||
};
|
||||
|
||||
export type WebhookPayloadType =
|
||||
@@ -222,6 +221,19 @@ export function isEventPayload(data: WebhookPayloadType): data is EventPayloadTy
|
||||
return !isNoShowPayload(data) && !isOOOEntryPayload(data) && !isDelegationCredentialErrorPayload(data);
|
||||
}
|
||||
|
||||
const webhookAssignmentReasonSchema = z.union([
|
||||
z.string(),
|
||||
z.array(z.object({ reasonEnum: z.string(), reasonString: z.string() })),
|
||||
z.null(),
|
||||
z.undefined(),
|
||||
]);
|
||||
|
||||
export function sanitizeAssignmentReasonForWebhook(data: EventPayloadType): EventPayloadType {
|
||||
const result = webhookAssignmentReasonSchema.safeParse(data.assignmentReason);
|
||||
if (result.success) return data;
|
||||
return { ...data, assignmentReason: undefined };
|
||||
}
|
||||
|
||||
const sendPayload = async (
|
||||
secretKey: string | null,
|
||||
triggerEvent: string,
|
||||
@@ -239,6 +251,7 @@ const sendPayload = async (
|
||||
let body;
|
||||
/* Zapier id is hardcoded in the DB, we send the raw data for this case */
|
||||
if (isEventPayload(data)) {
|
||||
data = sanitizeAssignmentReasonForWebhook(data);
|
||||
data.description = data.description || data.additionalNotes;
|
||||
if (appId === "zapier") {
|
||||
body = getZapierPayload({ ...data, createdAt });
|
||||
|
||||
@@ -2,8 +2,9 @@ import { getUsersCredentialsIncludeServiceAccountKey } from "@calcom/app-store/d
|
||||
import type { LocationObject } from "@calcom/app-store/locations";
|
||||
import { getLocationValueForDB } from "@calcom/app-store/locations";
|
||||
import { sendDeclinedEmailsAndSMS } from "@calcom/emails/email-manager";
|
||||
import type { Actor } from "@calcom/features/booking-audit/lib/dto/types";
|
||||
import type { ValidActionSource } from "@calcom/features/booking-audit/lib/types/actionSource";
|
||||
import { getBookingEventHandlerService } from "@calcom/features/bookings/di/BookingEventHandlerService.container";
|
||||
import { getFeaturesRepository } from "@calcom/features/di/containers/FeaturesRepository";
|
||||
import { getAllCredentialsIncludeServiceAccountKey } from "@calcom/features/bookings/lib/getAllCredentialsForUsersOnEvent/getAllCredentials";
|
||||
import { getAssignmentReasonCategory } from "@calcom/features/bookings/lib/getAssignmentReasonCategory";
|
||||
import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses";
|
||||
@@ -11,6 +12,8 @@ import { handleConfirmation } from "@calcom/features/bookings/lib/handleConfirma
|
||||
import { handleWebhookTrigger } from "@calcom/features/bookings/lib/handleWebhookTrigger";
|
||||
import { processPaymentRefund } from "@calcom/features/bookings/lib/payment/processPaymentRefund";
|
||||
import { BookingAccessService } from "@calcom/features/bookings/services/BookingAccessService";
|
||||
import { getFeaturesRepository } from "@calcom/features/di/containers/FeaturesRepository";
|
||||
import type { ISimpleLogger } from "@calcom/features/di/shared/services/logger.service";
|
||||
import { CreditService } from "@calcom/features/ee/billing/credit-service";
|
||||
import { getBookerBaseUrl } from "@calcom/features/ee/organizations/lib/getBookerUrlServer";
|
||||
import { workflowSelect } from "@calcom/features/ee/workflows/lib/getAllWorkflows";
|
||||
@@ -22,6 +25,8 @@ import getOrgIdFromMemberOrTeamId from "@calcom/lib/getOrgIdFromMemberOrTeamId";
|
||||
import { getTeamIdFromEventType } from "@calcom/lib/getTeamIdFromEventType";
|
||||
import { isPrismaObjOrUndefined } from "@calcom/lib/isPrismaObj";
|
||||
import { parseRecurringEvent } from "@calcom/lib/isRecurringEvent";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import { safeStringify } from "@calcom/lib/safeStringify";
|
||||
import { getTranslation } from "@calcom/lib/server/i18n";
|
||||
import { getTimeFormatStringFromUserTimeFormat } from "@calcom/lib/timeFormat";
|
||||
import type { TraceContext } from "@calcom/lib/tracing";
|
||||
@@ -34,11 +39,7 @@ import { TRPCError } from "@trpc/server";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import type { TrpcSessionUser } from "../../../types";
|
||||
import type { TConfirmInputSchema } from "./confirm.schema";
|
||||
import type { ValidActionSource } from "@calcom/features/booking-audit/lib/types/actionSource";
|
||||
import type { Actor } from "@calcom/features/booking-audit/lib/dto/types";
|
||||
import type { ISimpleLogger } from "@calcom/features/di/shared/services/logger.service";
|
||||
import { safeStringify } from "@calcom/lib/safeStringify";
|
||||
import logger from "@calcom/lib/logger";
|
||||
|
||||
type ConfirmOptions = {
|
||||
ctx: {
|
||||
user: Pick<
|
||||
@@ -537,8 +538,9 @@ export const confirmHandler = async ({ ctx, input }: ConfirmOptions) => {
|
||||
currency: booking.eventType?.currency,
|
||||
length: booking.eventType?.length,
|
||||
};
|
||||
const { assignmentReason: _emailAssignmentReason, ...evtWithoutAssignmentReason } = evt;
|
||||
const webhookData: EventPayloadType = {
|
||||
...evt,
|
||||
...evtWithoutAssignmentReason,
|
||||
...eventTypeInfo,
|
||||
bookingId,
|
||||
eventTypeId: booking.eventType?.id,
|
||||
|
||||
Reference in New Issue
Block a user