diff --git a/apps/web/public/static/locales/en/common.json b/apps/web/public/static/locales/en/common.json index af7919c155..b846f63e24 100644 --- a/apps/web/public/static/locales/en/common.json +++ b/apps/web/public/static/locales/en/common.json @@ -3441,6 +3441,35 @@ "usage_based_expiration_description": "This link can be used for {{count}} booking", "usage_based_generic_expiration_description": "This link can be configured to expire after a set number of bookings", "usage_based_expiration_description_plural": "This link can be used for {{count}} bookings", + "webhook_trigger_event": "The name of the trigger event (e.g., BOOKING_CREATED, BOOKING_CANCELLED)", + "webhook_created_at": "The time of the webhook", + "webhook_type": "The event type slug", + "webhook_title": "The event type name", + "webhook_start_time": "The event's start time", + "webhook_end_time": "The event's end time", + "webhook_description": "The event's description as described in the event type settings", + "webhook_location": "Location of the event", + "webhook_uid": "The UID of the booking", + "webhook_reschedule_uid": "The UID for rescheduling", + "webhook_cancellation_reason": "Reason for cancellation", + "webhook_rejection_reason": "Reason for rejection", + "webhook_organizer_name": "Name of the organizer", + "webhook_organizer_email": "Email of the organizer", + "webhook_organizer_timezone": "Timezone of the organizer (e.g., 'America/New_York', 'Asia/Kolkata')", + "webhook_organizer_locale": "Locale of the organizer (e.g., 'en', 'fr')", + "webhook_attendee_name": "Name of the first attendee", + "webhook_attendee_email": "Email of the first attendee", + "webhook_attendee_timezone": "Timezone of the first attendee", + "webhook_attendee_locale": "Locale of the first attendee", + "webhook_team_name": "Name of the team booked", + "webhook_team_members": "Members of the team booked", + "webhook_video_call_url": "Video call URL for the meeting", + "webhook_hide_variables": "Hide variables", + "webhook_show_variable": "Show available variables", + "webhook_event_and_booking": "Event and Booking", + "webhook_people": "People", + "webhook_teams": "Teams", + "webhook_metadata": "Metadata", "stats": "Stats", "booking_status": "Booking status", "ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Add your new strings above here ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑" diff --git a/packages/features/webhooks/components/WebhookForm.tsx b/packages/features/webhooks/components/WebhookForm.tsx index ff7a5d3b27..146b292021 100644 --- a/packages/features/webhooks/components/WebhookForm.tsx +++ b/packages/features/webhooks/components/WebhookForm.tsx @@ -73,6 +73,154 @@ const WEBHOOK_TRIGGER_EVENTS_GROUPED_BY_APP_V2: Record string) { + return [ + { + category: t("webhook_event_and_booking"), + variables: [ + { + name: "triggerEvent", + variable: "{{triggerEvent}}", + type: "String", + description: t("webhook_trigger_event"), + }, + { + name: "createdAt", + variable: "{{createdAt}}", + type: "Datetime", + description: t("webhook_created_at"), + }, + { name: "type", variable: "{{type}}", type: "String", description: t("webhook_type") }, + { name: "title", variable: "{{title}}", type: "String", description: t("webhook_title") }, + { + name: "startTime", + variable: "{{startTime}}", + type: "Datetime", + description: t("webhook_start_time"), + }, + { + name: "endTime", + variable: "{{endTime}}", + type: "Datetime", + description: t("webhook_end_time"), + }, + { + name: "description", + variable: "{{description}}", + type: "String", + description: t("webhook_description"), + }, + { + name: "location", + variable: "{{location}}", + type: "String", + description: t("webhook_location"), + }, + { name: "uid", variable: "{{uid}}", type: "String", description: t("webhook_uid") }, + { + name: "rescheduleUid", + variable: "{{rescheduleUid}}", + type: "String", + description: t("webhook_reschedule_uid"), + }, + { + name: "cancellationReason", + variable: "{{cancellationReason}}", + type: "String", + description: t("webhook_cancellation_reason"), + }, + { + name: "rejectionReason", + variable: "{{rejectionReason}}", + type: "String", + description: t("webhook_rejection_reason"), + }, + ], + }, + { + category: t("webhook_people"), + variables: [ + { + name: "organizer.name", + variable: "{{organizer.name}}", + type: "String", + description: t("webhook_organizer_name"), + }, + { + name: "organizer.email", + variable: "{{organizer.email}}", + type: "String", + description: t("webhook_organizer_email"), + }, + { + name: "organizer.timezone", + variable: "{{organizer.timezone}}", + type: "String", + description: t("webhook_organizer_timezone"), + }, + { + name: "organizer.language.locale", + variable: "{{organizer.language.locale}}", + type: "String", + description: t("webhook_organizer_locale"), + }, + { + name: "attendees.0.name", + variable: "{{attendees.0.name}}", + type: "String", + description: t("webhook_attendee_name"), + }, + { + name: "attendees.0.email", + variable: "{{attendees.0.email}}", + type: "String", + description: t("webhook_attendee_email"), + }, + { + name: "attendees.0.timezone", + variable: "{{attendees.0.timezone}}", + type: "String", + description: t("webhook_attendee_timezone"), + }, + { + name: "attendees.0.language.locale", + variable: "{{attendees.0.language.locale}}", + type: "String", + description: t("webhook_attendee_locale"), + }, + ], + }, + { + category: t("webhook_teams"), + variables: [ + { + name: "team.name", + variable: "{{team.name}}", + type: "String", + description: t("webhook_team_name"), + }, + { + name: "team.members", + variable: "{{team.members}}", + type: "String[]", + description: t("webhook_team_members"), + }, + ], + }, + { + category: t("webhook_metadata"), + variables: [ + { + name: "metadata.videoCallUrl", + variable: "{{metadata.videoCallUrl}}", + type: "String", + description: t("webhook_video_call_url"), + }, + ], + }, + ]; +} + export type WebhookFormValues = { subscriberUrl: string; active: boolean; @@ -94,6 +242,7 @@ const WebhookForm = (props: { }) => { const { apps = [], selectOnlyInstantMeetingOption = false, overrideTriggerOptions } = props; const { t } = useLocale(); + const webhookVariables = getWebhookVariables(t); const triggerOptions = overrideTriggerOptions ? [...overrideTriggerOptions] @@ -141,6 +290,29 @@ const WebhookForm = (props: { const [useCustomTemplate, setUseCustomTemplate] = useState( props?.webhook?.payloadTemplate !== undefined && props?.webhook?.payloadTemplate !== null ); + + function insertVariableIntoTemplate(current: string, name: string, value: string): string { + try { + const parsed = JSON.parse(current || "{}"); + parsed[name] = value; + return JSON.stringify(parsed, null, 2); + } catch { + const trimmed = current.trim(); + if (trimmed === "{}" || trimmed === "") { + return `{\n "${name}": "${value}"\n}`; + } + + if (trimmed.endsWith("}")) { + const withoutClosing = trimmed.slice(0, -1); + const needsComma = withoutClosing.trim().endsWith('"') || withoutClosing.trim().endsWith("}"); + return `${withoutClosing}${needsComma ? "," : ""}\n "${name}": "${value}"\n}`; + } + + return `${current}\n"${name}": "${value}"`; + } + } + + const [showVariables, setShowVariables] = useState(false); const [newSecret, setNewSecret] = useState(""); const [changeSecret, setChangeSecret] = useState(false); const hasSecretKey = !!props?.webhook?.secret; @@ -339,14 +511,52 @@ const WebhookForm = (props: { /> {useCustomTemplate && ( -