From b6648efa1e1913502a3b6022d97a2518d64a3baf Mon Sep 17 00:00:00 2001 From: aadamgough Date: Thu, 8 Jan 2026 17:51:10 -0800 Subject: [PATCH 1/5] grain trigger new requirements --- apps/sim/app/api/webhooks/route.ts | 5 ++++- apps/sim/tools/grain/create_hook.ts | 11 +++++++++++ apps/sim/tools/grain/list_hooks.ts | 1 + apps/sim/tools/grain/types.ts | 2 ++ 4 files changed, 18 insertions(+), 1 deletion(-) diff --git a/apps/sim/app/api/webhooks/route.ts b/apps/sim/app/api/webhooks/route.ts index e300294a21..c0e02a532f 100644 --- a/apps/sim/app/api/webhooks/route.ts +++ b/apps/sim/app/api/webhooks/route.ts @@ -1197,6 +1197,7 @@ async function createGrainWebhookSubscription( const requestBody: Record = { hook_url: notificationUrl, + hook_type: 'recording_added', // Required parameter - fires when a new recording is added } // Build include object based on configuration @@ -1226,8 +1227,10 @@ async function createGrainWebhookSubscription( const responseBody = await grainResponse.json() - if (!grainResponse.ok || responseBody.error) { + if (!grainResponse.ok || responseBody.error || responseBody.errors) { + logger.warn('[App] Grain response body:', responseBody) const errorMessage = + responseBody.errors?.detail || responseBody.error?.message || responseBody.error || responseBody.message || diff --git a/apps/sim/tools/grain/create_hook.ts b/apps/sim/tools/grain/create_hook.ts index b1b4709f24..9ce40e5307 100644 --- a/apps/sim/tools/grain/create_hook.ts +++ b/apps/sim/tools/grain/create_hook.ts @@ -20,6 +20,12 @@ export const grainCreateHookTool: ToolConfig { const body: Record = { hook_url: params.hookUrl, + hook_type: params.hookType, } const filter: Record = {} @@ -147,6 +154,10 @@ export const grainCreateHookTool: ToolConfig Date: Thu, 8 Jan 2026 19:20:40 -0800 Subject: [PATCH 2/5] removed comment --- apps/sim/app/api/webhooks/route.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/sim/app/api/webhooks/route.ts b/apps/sim/app/api/webhooks/route.ts index c0e02a532f..7e50e71974 100644 --- a/apps/sim/app/api/webhooks/route.ts +++ b/apps/sim/app/api/webhooks/route.ts @@ -1197,7 +1197,7 @@ async function createGrainWebhookSubscription( const requestBody: Record = { hook_url: notificationUrl, - hook_type: 'recording_added', // Required parameter - fires when a new recording is added + hook_type: 'recording_added', } // Build include object based on configuration From 12617f6d6c05235d4d94db3a15718783f57634ee Mon Sep 17 00:00:00 2001 From: aadamgough Date: Thu, 8 Jan 2026 19:40:53 -0800 Subject: [PATCH 3/5] made it generic for all triggers --- apps/sim/app/api/webhooks/route.ts | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/apps/sim/app/api/webhooks/route.ts b/apps/sim/app/api/webhooks/route.ts index 7e50e71974..37ea827011 100644 --- a/apps/sim/app/api/webhooks/route.ts +++ b/apps/sim/app/api/webhooks/route.ts @@ -1179,7 +1179,7 @@ async function createGrainWebhookSubscription( ): Promise { try { const { path, providerConfig } = webhookData - const { apiKey, includeHighlights, includeParticipants, includeAiSummary } = + const { apiKey, triggerId, includeHighlights, includeParticipants, includeAiSummary } = providerConfig || {} if (!apiKey) { @@ -1191,13 +1191,38 @@ async function createGrainWebhookSubscription( ) } + const hookTypeMap: Record = { + grain_webhook: 'recording_added', + grain_recording_created: 'recording_added', + grain_recording_updated: 'recording_added', + grain_highlight_created: 'recording_added', + grain_highlight_updated: 'recording_added', + grain_story_created: 'recording_added', + grain_upload_status: 'upload_status', + } + + const hookType = hookTypeMap[triggerId] ?? 'recording_added' + if (!hookTypeMap[triggerId]) { + logger.warn( + `[${requestId}] Unknown triggerId for Grain: ${triggerId}, defaulting to recording_added`, + { + webhookId: webhookData.id, + } + ) + } + + logger.info(`[${requestId}] Creating Grain webhook with hook_type: ${hookType}`, { + triggerId, + webhookId: webhookData.id, + }) + const notificationUrl = `${getBaseUrl()}/api/webhooks/trigger/${path}` const grainApiUrl = 'https://api.grain.com/_/public-api/v2/hooks/create' const requestBody: Record = { hook_url: notificationUrl, - hook_type: 'recording_added', + hook_type: hookType, } // Build include object based on configuration From 46740c925bc3d45eb60c13a0dc17f6d87c358450 Mon Sep 17 00:00:00 2001 From: aadamgough Date: Thu, 8 Jan 2026 19:53:38 -0800 Subject: [PATCH 4/5] fire only for specific trigger type --- apps/sim/app/api/webhooks/route.ts | 35 +++++++++++++++++++++++------- apps/sim/lib/webhooks/processor.ts | 14 ++++++++++++ 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/apps/sim/app/api/webhooks/route.ts b/apps/sim/app/api/webhooks/route.ts index 37ea827011..7fcfcde4bd 100644 --- a/apps/sim/app/api/webhooks/route.ts +++ b/apps/sim/app/api/webhooks/route.ts @@ -744,7 +744,7 @@ export async function POST(request: NextRequest) { if (savedWebhook && provider === 'grain') { logger.info(`[${requestId}] Grain provider detected. Creating Grain webhook subscription.`) try { - const grainHookId = await createGrainWebhookSubscription( + const grainResult = await createGrainWebhookSubscription( request, { id: savedWebhook.id, @@ -754,11 +754,12 @@ export async function POST(request: NextRequest) { requestId ) - if (grainHookId) { - // Update the webhook record with the external Grain hook ID + if (grainResult) { + // Update the webhook record with the external Grain hook ID and event types for filtering const updatedConfig = { ...(savedWebhook.providerConfig as Record), - externalId: grainHookId, + externalId: grainResult.id, + eventTypes: grainResult.eventTypes, } await db .update(webhook) @@ -770,7 +771,8 @@ export async function POST(request: NextRequest) { savedWebhook.providerConfig = updatedConfig logger.info(`[${requestId}] Successfully created Grain webhook`, { - grainHookId, + grainHookId: grainResult.id, + eventTypes: grainResult.eventTypes, webhookId: savedWebhook.id, }) } @@ -1176,7 +1178,7 @@ async function createGrainWebhookSubscription( request: NextRequest, webhookData: any, requestId: string -): Promise { +): Promise<{ id: string; eventTypes: string[] } | undefined> { try { const { path, providerConfig } = webhookData const { apiKey, triggerId, includeHighlights, includeParticipants, includeAiSummary } = @@ -1191,6 +1193,7 @@ async function createGrainWebhookSubscription( ) } + // Map trigger IDs to Grain API hook_type (only 2 options: recording_added, upload_status) const hookTypeMap: Record = { grain_webhook: 'recording_added', grain_recording_created: 'recording_added', @@ -1201,7 +1204,20 @@ async function createGrainWebhookSubscription( grain_upload_status: 'upload_status', } + // Map trigger IDs to expected payload event types (for filtering incoming webhooks) + const eventTypeMap: Record = { + grain_webhook: [], + grain_recording_created: ['recording_added'], + grain_recording_updated: ['recording_updated'], + grain_highlight_created: ['highlight_created'], + grain_highlight_updated: ['highlight_updated'], + grain_story_created: ['story_created'], + grain_upload_status: ['upload_status'], + } + const hookType = hookTypeMap[triggerId] ?? 'recording_added' + const eventTypes = eventTypeMap[triggerId] ?? [] + if (!hookTypeMap[triggerId]) { logger.warn( `[${requestId}] Unknown triggerId for Grain: ${triggerId}, defaulting to recording_added`, @@ -1211,8 +1227,10 @@ async function createGrainWebhookSubscription( ) } - logger.info(`[${requestId}] Creating Grain webhook with hook_type: ${hookType}`, { + logger.info(`[${requestId}] Creating Grain webhook`, { triggerId, + hookType, + eventTypes, webhookId: webhookData.id, }) @@ -1283,10 +1301,11 @@ async function createGrainWebhookSubscription( `[${requestId}] Successfully created webhook in Grain for webhook ${webhookData.id}.`, { grainWebhookId: responseBody.id, + eventTypes, } ) - return responseBody.id + return { id: responseBody.id, eventTypes } } catch (error: any) { logger.error( `[${requestId}] Exception during Grain webhook creation for webhook ${webhookData.id}.`, diff --git a/apps/sim/lib/webhooks/processor.ts b/apps/sim/lib/webhooks/processor.ts index 65fbf43050..468574c3df 100644 --- a/apps/sim/lib/webhooks/processor.ts +++ b/apps/sim/lib/webhooks/processor.ts @@ -239,6 +239,20 @@ export function shouldSkipWebhookEvent(webhook: any, body: any, requestId: strin } } + // Grain event filtering - filter by payload type field (e.g., recording_added, highlight_created) + if (webhook.provider === 'grain') { + const eventTypes = providerConfig.eventTypes + if (eventTypes && Array.isArray(eventTypes) && eventTypes.length > 0) { + const eventType = body?.type + if (eventType && !eventTypes.includes(eventType)) { + logger.info( + `[${requestId}] Grain event type '${eventType}' not in allowed list for webhook ${webhook.id}, skipping` + ) + return true + } + } + } + return false } From a40cb112b7e9e72fc0203019eee354e5e318606e Mon Sep 17 00:00:00 2001 From: aadamgough Date: Thu, 8 Jan 2026 19:59:06 -0800 Subject: [PATCH 5/5] removed comments --- apps/sim/app/api/webhooks/route.ts | 1 - apps/sim/lib/webhooks/processor.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/apps/sim/app/api/webhooks/route.ts b/apps/sim/app/api/webhooks/route.ts index 7fcfcde4bd..a26fe9933b 100644 --- a/apps/sim/app/api/webhooks/route.ts +++ b/apps/sim/app/api/webhooks/route.ts @@ -1204,7 +1204,6 @@ async function createGrainWebhookSubscription( grain_upload_status: 'upload_status', } - // Map trigger IDs to expected payload event types (for filtering incoming webhooks) const eventTypeMap: Record = { grain_webhook: [], grain_recording_created: ['recording_added'], diff --git a/apps/sim/lib/webhooks/processor.ts b/apps/sim/lib/webhooks/processor.ts index 468574c3df..5d7d847658 100644 --- a/apps/sim/lib/webhooks/processor.ts +++ b/apps/sim/lib/webhooks/processor.ts @@ -239,7 +239,6 @@ export function shouldSkipWebhookEvent(webhook: any, body: any, requestId: strin } } - // Grain event filtering - filter by payload type field (e.g., recording_added, highlight_created) if (webhook.provider === 'grain') { const eventTypes = providerConfig.eventTypes if (eventTypes && Array.isArray(eventTypes) && eventTypes.length > 0) {