@@ -581,6 +581,56 @@ export async function POST(request: NextRequest) {
581581 }
582582 // --- End RSS specific logic ---
583583
584+ if ( savedWebhook && provider === 'grain' ) {
585+ logger . info ( `[${ requestId } ] Grain provider detected. Creating Grain webhook subscription.` )
586+ try {
587+ const grainHookId = await createGrainWebhookSubscription (
588+ request ,
589+ {
590+ id : savedWebhook . id ,
591+ path : savedWebhook . path ,
592+ providerConfig : savedWebhook . providerConfig ,
593+ } ,
594+ requestId
595+ )
596+
597+ if ( grainHookId ) {
598+ // Update the webhook record with the external Grain hook ID
599+ const updatedConfig = {
600+ ...( savedWebhook . providerConfig as Record < string , any > ) ,
601+ externalId : grainHookId ,
602+ }
603+ await db
604+ . update ( webhook )
605+ . set ( {
606+ providerConfig : updatedConfig ,
607+ updatedAt : new Date ( ) ,
608+ } )
609+ . where ( eq ( webhook . id , savedWebhook . id ) )
610+
611+ savedWebhook . providerConfig = updatedConfig
612+ logger . info ( `[${ requestId } ] Successfully created Grain webhook` , {
613+ grainHookId,
614+ webhookId : savedWebhook . id ,
615+ } )
616+ }
617+ } catch ( err ) {
618+ logger . error (
619+ `[${ requestId } ] Error creating Grain webhook subscription, rolling back webhook` ,
620+ err
621+ )
622+ await db . delete ( webhook ) . where ( eq ( webhook . id , savedWebhook . id ) )
623+ return NextResponse . json (
624+ {
625+ error : 'Failed to create webhook in Grain' ,
626+ details : err instanceof Error ? err . message : 'Unknown error' ,
627+ } ,
628+ { status : 500 }
629+ )
630+ }
631+ }
632+ // --- End Grain specific logic ---
633+
584634 const status = targetWebhookId ? 200 : 201
585635 return NextResponse . json ( { webhook : savedWebhook } , { status } )
586636 } catch ( error : any ) {
@@ -947,3 +997,103 @@ async function createWebflowWebhookSubscription(
947997 throw error
948998 }
949999}
1000+
1001+ // Helper function to create the webhook subscription in Grain
1002+ async function createGrainWebhookSubscription (
1003+ request : NextRequest ,
1004+ webhookData : any ,
1005+ requestId : string
1006+ ) : Promise < string | undefined > {
1007+ try {
1008+ const { path, providerConfig } = webhookData
1009+ const { apiKey, includeHighlights, includeParticipants, includeAiSummary } =
1010+ providerConfig || { }
1011+
1012+ if ( ! apiKey ) {
1013+ logger . warn ( `[${ requestId } ] Missing apiKey for Grain webhook creation.` , {
1014+ webhookId : webhookData . id ,
1015+ } )
1016+ throw new Error (
1017+ 'Grain API Key is required. Please provide your Grain Personal Access Token in the trigger configuration.'
1018+ )
1019+ }
1020+
1021+ const notificationUrl = `${ getBaseUrl ( ) } /api/webhooks/trigger/${ path } `
1022+
1023+ const grainApiUrl = 'https://api.grain.com/_/public-api/v2/hooks/create'
1024+
1025+ const requestBody : Record < string , any > = {
1026+ hook_url : notificationUrl ,
1027+ }
1028+
1029+ // Build include object based on configuration
1030+ const include : Record < string , boolean > = { }
1031+ if ( includeHighlights ) {
1032+ include . highlights = true
1033+ }
1034+ if ( includeParticipants ) {
1035+ include . participants = true
1036+ }
1037+ if ( includeAiSummary ) {
1038+ include . ai_summary = true
1039+ }
1040+ if ( Object . keys ( include ) . length > 0 ) {
1041+ requestBody . include = include
1042+ }
1043+
1044+ const grainResponse = await fetch ( grainApiUrl , {
1045+ method : 'POST' ,
1046+ headers : {
1047+ Authorization : `Bearer ${ apiKey } ` ,
1048+ 'Content-Type' : 'application/json' ,
1049+ 'Public-Api-Version' : '2025-10-31' ,
1050+ } ,
1051+ body : JSON . stringify ( requestBody ) ,
1052+ } )
1053+
1054+ const responseBody = await grainResponse . json ( )
1055+
1056+ if ( ! grainResponse . ok || responseBody . error ) {
1057+ const errorMessage =
1058+ responseBody . error ?. message ||
1059+ responseBody . error ||
1060+ responseBody . message ||
1061+ 'Unknown Grain API error'
1062+ logger . error (
1063+ `[${ requestId } ] Failed to create webhook in Grain for webhook ${ webhookData . id } . Status: ${ grainResponse . status } ` ,
1064+ { message : errorMessage , response : responseBody }
1065+ )
1066+
1067+ let userFriendlyMessage = 'Failed to create webhook subscription in Grain'
1068+ if ( grainResponse . status === 401 ) {
1069+ userFriendlyMessage =
1070+ 'Invalid Grain API Key. Please verify your Personal Access Token is correct.'
1071+ } else if ( grainResponse . status === 403 ) {
1072+ userFriendlyMessage =
1073+ 'Access denied. Please ensure your Grain API Key has appropriate permissions.'
1074+ } else if ( errorMessage && errorMessage !== 'Unknown Grain API error' ) {
1075+ userFriendlyMessage = `Grain error: ${ errorMessage } `
1076+ }
1077+
1078+ throw new Error ( userFriendlyMessage )
1079+ }
1080+
1081+ logger . info (
1082+ `[${ requestId } ] Successfully created webhook in Grain for webhook ${ webhookData . id } .` ,
1083+ {
1084+ grainWebhookId : responseBody . id ,
1085+ }
1086+ )
1087+
1088+ return responseBody . id
1089+ } catch ( error : any ) {
1090+ logger . error (
1091+ `[${ requestId } ] Exception during Grain webhook creation for webhook ${ webhookData . id } .` ,
1092+ {
1093+ message : error . message ,
1094+ stack : error . stack ,
1095+ }
1096+ )
1097+ throw error
1098+ }
1099+ }
0 commit comments