@@ -297,6 +297,31 @@ export async function POST(request: NextRequest) {
297297 }
298298 }
299299
300+ if ( provider === 'calendly' ) {
301+ logger . info ( `[${ requestId } ] Creating Calendly subscription before saving to database` )
302+ try {
303+ externalSubscriptionId = await createCalendlyWebhookSubscription (
304+ request ,
305+ userId ,
306+ createTempWebhookData ( ) ,
307+ requestId
308+ )
309+ if ( externalSubscriptionId ) {
310+ resolvedProviderConfig . externalId = externalSubscriptionId
311+ externalSubscriptionCreated = true
312+ }
313+ } catch ( err ) {
314+ logger . error ( `[${ requestId } ] Error creating Calendly webhook subscription` , err )
315+ return NextResponse . json (
316+ {
317+ error : 'Failed to create webhook in Calendly' ,
318+ details : err instanceof Error ? err . message : 'Unknown error' ,
319+ } ,
320+ { status : 500 }
321+ )
322+ }
323+ }
324+
300325 if ( provider === 'microsoft-teams' ) {
301326 const { createTeamsSubscription } = await import ( '@/lib/webhooks/webhook-helpers' )
302327 logger . info ( `[${ requestId } ] Creating Teams subscription before saving to database` )
@@ -635,6 +660,140 @@ async function createAirtableWebhookSubscription(
635660 throw error
636661 }
637662}
663+
664+ // Helper function to create the webhook subscription in Calendly
665+ async function createCalendlyWebhookSubscription (
666+ request : NextRequest ,
667+ userId : string ,
668+ webhookData : any ,
669+ requestId : string
670+ ) : Promise < string | undefined > {
671+ try {
672+ const { path, providerConfig } = webhookData
673+ const { apiKey, organization, triggerId } = providerConfig || { }
674+
675+ if ( ! apiKey ) {
676+ logger . warn ( `[${ requestId } ] Missing apiKey for Calendly webhook creation.` , {
677+ webhookId : webhookData . id ,
678+ } )
679+ throw new Error (
680+ 'Personal Access Token is required to create Calendly webhook. Please provide your Calendly Personal Access Token.'
681+ )
682+ }
683+
684+ if ( ! organization ) {
685+ logger . warn ( `[${ requestId } ] Missing organization URI for Calendly webhook creation.` , {
686+ webhookId : webhookData . id ,
687+ } )
688+ throw new Error (
689+ 'Organization URI is required to create Calendly webhook. Please provide your Organization URI from the "Get Current User" operation.'
690+ )
691+ }
692+
693+ if ( ! triggerId ) {
694+ logger . warn ( `[${ requestId } ] Missing triggerId for Calendly webhook creation.` , {
695+ webhookId : webhookData . id ,
696+ } )
697+ throw new Error ( 'Trigger ID is required to create Calendly webhook' )
698+ }
699+
700+ const notificationUrl = `${ getBaseUrl ( ) } /api/webhooks/trigger/${ path } `
701+
702+ // Map trigger IDs to Calendly event types
703+ const eventTypeMap : Record < string , string [ ] > = {
704+ calendly_invitee_created : [ 'invitee.created' ] ,
705+ calendly_invitee_canceled : [ 'invitee.canceled' ] ,
706+ calendly_routing_form_submitted : [ 'routing_form_submission.created' ] ,
707+ calendly_webhook : [ 'invitee.created' , 'invitee.canceled' , 'routing_form_submission.created' ] ,
708+ }
709+
710+ const events = eventTypeMap [ triggerId ] || [ 'invitee.created' ]
711+
712+ const calendlyApiUrl = 'https://api.calendly.com/webhook_subscriptions'
713+
714+ const requestBody = {
715+ url : notificationUrl ,
716+ events,
717+ organization,
718+ scope : 'organization' ,
719+ }
720+
721+ const calendlyResponse = await fetch ( calendlyApiUrl , {
722+ method : 'POST' ,
723+ headers : {
724+ Authorization : `Bearer ${ apiKey } ` ,
725+ 'Content-Type' : 'application/json' ,
726+ } ,
727+ body : JSON . stringify ( requestBody ) ,
728+ } )
729+
730+ if ( ! calendlyResponse . ok ) {
731+ const errorBody = await calendlyResponse . json ( ) . catch ( ( ) => ( { } ) )
732+ const errorMessage = errorBody . message || errorBody . title || 'Unknown Calendly API error'
733+ logger . error (
734+ `[${ requestId } ] Failed to create webhook in Calendly for webhook ${ webhookData . id } . Status: ${ calendlyResponse . status } ` ,
735+ { response : errorBody }
736+ )
737+
738+ let userFriendlyMessage = 'Failed to create webhook subscription in Calendly'
739+ if ( calendlyResponse . status === 401 ) {
740+ userFriendlyMessage =
741+ 'Calendly authentication failed. Please verify your Personal Access Token is correct.'
742+ } else if ( calendlyResponse . status === 403 ) {
743+ userFriendlyMessage =
744+ 'Calendly access denied. Please ensure you have appropriate permissions and a paid Calendly subscription.'
745+ } else if ( calendlyResponse . status === 404 ) {
746+ userFriendlyMessage =
747+ 'Calendly organization not found. Please verify the Organization URI is correct.'
748+ } else if ( errorMessage && errorMessage !== 'Unknown Calendly API error' ) {
749+ userFriendlyMessage = `Calendly error: ${ errorMessage } `
750+ }
751+
752+ throw new Error ( userFriendlyMessage )
753+ }
754+
755+ const responseBody = await calendlyResponse . json ( )
756+ const webhookUri = responseBody . resource ?. uri
757+
758+ if ( ! webhookUri ) {
759+ logger . error (
760+ `[${ requestId } ] Calendly webhook created but no webhook URI returned for webhook ${ webhookData . id } ` ,
761+ { response : responseBody }
762+ )
763+ throw new Error ( 'Calendly webhook creation succeeded but no webhook URI was returned' )
764+ }
765+
766+ // Extract the webhook ID from the URI (e.g., https://api.calendly.com/webhook_subscriptions/WEBHOOK_ID)
767+ const webhookId = webhookUri . split ( '/' ) . pop ( )
768+
769+ if ( ! webhookId ) {
770+ logger . error ( `[${ requestId } ] Could not extract webhook ID from Calendly URI: ${ webhookUri } ` , {
771+ response : responseBody ,
772+ } )
773+ throw new Error ( 'Failed to extract webhook ID from Calendly response' )
774+ }
775+
776+ logger . info (
777+ `[${ requestId } ] Successfully created webhook in Calendly for webhook ${ webhookData . id } .` ,
778+ {
779+ calendlyWebhookUri : webhookUri ,
780+ calendlyWebhookId : webhookId ,
781+ }
782+ )
783+ return webhookId
784+ } catch ( error : any ) {
785+ logger . error (
786+ `[${ requestId } ] Exception during Calendly webhook creation for webhook ${ webhookData . id } .` ,
787+ {
788+ message : error . message ,
789+ stack : error . stack ,
790+ }
791+ )
792+ // Re-throw the error so it can be caught by the outer try-catch
793+ throw error
794+ }
795+ }
796+
638797// Helper function to create the webhook subscription in Webflow
639798async function createWebflowWebhookSubscription (
640799 request : NextRequest ,
0 commit comments