@@ -73,6 +73,154 @@ const WEBHOOK_TRIGGER_EVENTS_GROUPED_BY_APP_V2: Record<string, WebhookTriggerEve
73
73
] ,
74
74
} as const ;
75
75
76
+ function getWebhookVariables ( t : ( key : string ) => string ) {
77
+ return [
78
+ {
79
+ category : t ( "webhook_event_and_booking" ) ,
80
+ variables : [
81
+ {
82
+ name : "triggerEvent" ,
83
+ variable : "{{triggerEvent}}" ,
84
+ type : "String" ,
85
+ description : t ( "webhook_trigger_event" ) ,
86
+ } ,
87
+ {
88
+ name : "createdAt" ,
89
+ variable : "{{createdAt}}" ,
90
+ type : "Datetime" ,
91
+ description : t ( "webhook_created_at" ) ,
92
+ } ,
93
+ { name : "type" , variable : "{{type}}" , type : "String" , description : t ( "webhook_type" ) } ,
94
+ { name : "title" , variable : "{{title}}" , type : "String" , description : t ( "webhook_title" ) } ,
95
+ {
96
+ name : "startTime" ,
97
+ variable : "{{startTime}}" ,
98
+ type : "Datetime" ,
99
+ description : t ( "webhook_start_time" ) ,
100
+ } ,
101
+ {
102
+ name : "endTime" ,
103
+ variable : "{{endTime}}" ,
104
+ type : "Datetime" ,
105
+ description : t ( "webhook_end_time" ) ,
106
+ } ,
107
+ {
108
+ name : "description" ,
109
+ variable : "{{description}}" ,
110
+ type : "String" ,
111
+ description : t ( "webhook_description" ) ,
112
+ } ,
113
+ {
114
+ name : "location" ,
115
+ variable : "{{location}}" ,
116
+ type : "String" ,
117
+ description : t ( "webhook_location" ) ,
118
+ } ,
119
+ { name : "uid" , variable : "{{uid}}" , type : "String" , description : t ( "webhook_uid" ) } ,
120
+ {
121
+ name : "rescheduleUid" ,
122
+ variable : "{{rescheduleUid}}" ,
123
+ type : "String" ,
124
+ description : t ( "webhook_reschedule_uid" ) ,
125
+ } ,
126
+ {
127
+ name : "cancellationReason" ,
128
+ variable : "{{cancellationReason}}" ,
129
+ type : "String" ,
130
+ description : t ( "webhook_cancellation_reason" ) ,
131
+ } ,
132
+ {
133
+ name : "rejectionReason" ,
134
+ variable : "{{rejectionReason}}" ,
135
+ type : "String" ,
136
+ description : t ( "webhook_rejection_reason" ) ,
137
+ } ,
138
+ ] ,
139
+ } ,
140
+ {
141
+ category : t ( "webhook_people" ) ,
142
+ variables : [
143
+ {
144
+ name : "organizer.name" ,
145
+ variable : "{{organizer.name}}" ,
146
+ type : "String" ,
147
+ description : t ( "webhook_organizer_name" ) ,
148
+ } ,
149
+ {
150
+ name : "organizer.email" ,
151
+ variable : "{{organizer.email}}" ,
152
+ type : "String" ,
153
+ description : t ( "webhook_organizer_email" ) ,
154
+ } ,
155
+ {
156
+ name : "organizer.timezone" ,
157
+ variable : "{{organizer.timezone}}" ,
158
+ type : "String" ,
159
+ description : t ( "webhook_organizer_timezone" ) ,
160
+ } ,
161
+ {
162
+ name : "organizer.language.locale" ,
163
+ variable : "{{organizer.language.locale}}" ,
164
+ type : "String" ,
165
+ description : t ( "webhook_organizer_locale" ) ,
166
+ } ,
167
+ {
168
+ name : "attendees.0.name" ,
169
+ variable : "{{attendees.0.name}}" ,
170
+ type : "String" ,
171
+ description : t ( "webhook_attendee_name" ) ,
172
+ } ,
173
+ {
174
+ name : "attendees.0.email" ,
175
+ variable : "{{attendees.0.email}}" ,
176
+ type : "String" ,
177
+ description : t ( "webhook_attendee_email" ) ,
178
+ } ,
179
+ {
180
+ name : "attendees.0.timezone" ,
181
+ variable : "{{attendees.0.timezone}}" ,
182
+ type : "String" ,
183
+ description : t ( "webhook_attendee_timezone" ) ,
184
+ } ,
185
+ {
186
+ name : "attendees.0.language.locale" ,
187
+ variable : "{{attendees.0.language.locale}}" ,
188
+ type : "String" ,
189
+ description : t ( "webhook_attendee_locale" ) ,
190
+ } ,
191
+ ] ,
192
+ } ,
193
+ {
194
+ category : t ( "webhook_teams" ) ,
195
+ variables : [
196
+ {
197
+ name : "team.name" ,
198
+ variable : "{{team.name}}" ,
199
+ type : "String" ,
200
+ description : t ( "webhook_team_name" ) ,
201
+ } ,
202
+ {
203
+ name : "team.members" ,
204
+ variable : "{{team.members}}" ,
205
+ type : "String[]" ,
206
+ description : t ( "webhook_team_members" ) ,
207
+ } ,
208
+ ] ,
209
+ } ,
210
+ {
211
+ category : t ( "webhook_metadata" ) ,
212
+ variables : [
213
+ {
214
+ name : "metadata.videoCallUrl" ,
215
+ variable : "{{metadata.videoCallUrl}}" ,
216
+ type : "String" ,
217
+ description : t ( "webhook_video_call_url" ) ,
218
+ } ,
219
+ ] ,
220
+ } ,
221
+ ] ;
222
+ }
223
+
76
224
export type WebhookFormValues = {
77
225
subscriberUrl : string ;
78
226
active : boolean ;
@@ -94,6 +242,7 @@ const WebhookForm = (props: {
94
242
} ) => {
95
243
const { apps = [ ] , selectOnlyInstantMeetingOption = false , overrideTriggerOptions } = props ;
96
244
const { t } = useLocale ( ) ;
245
+ const webhookVariables = getWebhookVariables ( t ) ;
97
246
98
247
const triggerOptions = overrideTriggerOptions
99
248
? [ ...overrideTriggerOptions ]
@@ -141,6 +290,29 @@ const WebhookForm = (props: {
141
290
const [ useCustomTemplate , setUseCustomTemplate ] = useState (
142
291
props ?. webhook ?. payloadTemplate !== undefined && props ?. webhook ?. payloadTemplate !== null
143
292
) ;
293
+
294
+ function insertVariableIntoTemplate ( current : string , name : string , value : string ) : string {
295
+ try {
296
+ const parsed = JSON . parse ( current || "{}" ) ;
297
+ parsed [ name ] = value ;
298
+ return JSON . stringify ( parsed , null , 2 ) ;
299
+ } catch {
300
+ const trimmed = current . trim ( ) ;
301
+ if ( trimmed === "{}" || trimmed === "" ) {
302
+ return `{\n "${ name } ": "${ value } "\n}` ;
303
+ }
304
+
305
+ if ( trimmed . endsWith ( "}" ) ) {
306
+ const withoutClosing = trimmed . slice ( 0 , - 1 ) ;
307
+ const needsComma = withoutClosing . trim ( ) . endsWith ( '"' ) || withoutClosing . trim ( ) . endsWith ( "}" ) ;
308
+ return `${ withoutClosing } ${ needsComma ? "," : "" } \n "${ name } ": "${ value } "\n}` ;
309
+ }
310
+
311
+ return `${ current } \n"${ name } ": "${ value } "` ;
312
+ }
313
+ }
314
+
315
+ const [ showVariables , setShowVariables ] = useState ( false ) ;
144
316
const [ newSecret , setNewSecret ] = useState ( "" ) ;
145
317
const [ changeSecret , setChangeSecret ] = useState < boolean > ( false ) ;
146
318
const hasSecretKey = ! ! props ?. webhook ?. secret ;
@@ -339,14 +511,52 @@ const WebhookForm = (props: {
339
511
/>
340
512
</ div >
341
513
{ useCustomTemplate && (
342
- < TextArea
343
- name = "customPayloadTemplate"
344
- rows = { 3 }
345
- value = { value }
346
- onChange = { ( e ) => {
347
- formMethods . setValue ( "payloadTemplate" , e ?. target . value , { shouldDirty : true } ) ;
348
- } }
349
- />
514
+ < div className = "space-y-3" >
515
+ < TextArea
516
+ name = "customPayloadTemplate"
517
+ rows = { 8 }
518
+ value = { value || "" }
519
+ placeholder = { `{\n\n}` }
520
+ onChange = { ( e ) =>
521
+ formMethods . setValue ( "payloadTemplate" , e ?. target . value , { shouldDirty : true } )
522
+ }
523
+ />
524
+
525
+ < Button type = "button" color = "secondary" onClick = { ( ) => setShowVariables ( ! showVariables ) } >
526
+ { showVariables ? t ( "webhook_hide_variables" ) : t ( "webhook_show_variable" ) }
527
+ </ Button >
528
+
529
+ { showVariables && (
530
+ < div className = "border-muted max-h-80 overflow-y-auto rounded-md border p-3" >
531
+ { webhookVariables . map ( ( { category, variables } ) => (
532
+ < div key = { category } className = "mb-4" >
533
+ < h4 className = "mb-2 text-sm font-medium" > { category } </ h4 >
534
+ < div className = "space-y-2" >
535
+ { variables . map ( ( { name, variable, description } ) => (
536
+ < div
537
+ key = { name }
538
+ className = "hover:bg-muted cursor-pointer rounded p-2 text-sm transition-colors"
539
+ onClick = { ( ) => {
540
+ const currentValue = formMethods . getValues ( "payloadTemplate" ) || "{}" ;
541
+ const updatedValue = insertVariableIntoTemplate (
542
+ currentValue ,
543
+ name ,
544
+ variable
545
+ ) ;
546
+ formMethods . setValue ( "payloadTemplate" , updatedValue , {
547
+ shouldDirty : true ,
548
+ } ) ;
549
+ } } >
550
+ < div className = "text-emphasis font-mono" > { variable } </ div >
551
+ < div className = "text-muted mt-1 text-xs" > { description } </ div >
552
+ </ div >
553
+ ) ) }
554
+ </ div >
555
+ </ div >
556
+ ) ) }
557
+ </ div >
558
+ ) }
559
+ </ div >
350
560
) }
351
561
</ >
352
562
) }
0 commit comments