Skip to content

Conversation

@debojyoti-git
Copy link
Contributor

@debojyoti-git debojyoti-git commented Sep 25, 2025

Description

Testing

Additional Notes

Summary by CodeRabbit

  • New Features

    • Webhook trigger + guided configuration panel in the Workflow Builder (path, HTTP method, auth, headers, query params, response mode, generated URL with copy).
    • Create/update webhook steps from the canvas; webhook lifecycle, registration and reloads supported; incoming webhook endpoints execute workflows and report execution status.
    • Credential management UI: modal, selector (create/edit/delete), testing, and credential-backed webhook auth.
  • Style

    • Removed a Close icon from the workflow UI.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 25, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds webhook support end-to-end: new WebhookConfig type and UI, credential management components and API, WorkflowBuilder webhook wiring, server webhook runtime (registry, auth, execution, integration), scripts, and related services; also removes an icon export.

Changes

Cohort / File(s) Summary
Types
frontend/src/components/workflow/Types.ts
Adds exported WebhookConfig interface (webhookUrl, httpMethod, path, authentication, responseMode; optional selectedCredential, options, headers, queryParams).
Frontend — Webhook UI
frontend/src/components/workflow/WebhookConfigurationUI.tsx
Adds WebhookConfigurationUI component (default + named export) and re-exports WebhookConfig; panel for path/method/auth/headers/query params, copyable URL, validation, and onSave wiring.
Frontend — Workflow integration
frontend/src/components/workflow/WorkflowBuilder.tsx
Integrates webhook nodes/triggers and WebhookConfigurationUI: rendering, create/update flows (pending node handling), state/handlers, trigger wiring, and save/back behaviors.
Frontend — Credentials UX
frontend/src/components/CredentialModal.tsx, frontend/src/components/CredentialExample.tsx, frontend/src/components/workflow/CredentialSelector.tsx
Adds CredentialModal (+ CredentialData), CredentialExample demo, and CredentialSelector component with create/edit/delete/test flows and modal integration.
Frontend — Credentials API
frontend/src/components/workflow/api/ApiHandlers.ts
Adds Credential interface, credentialsAPI (fetchAll/fetchByType/create/update/delete/test), and extends workflowToolsAPI with saveWebhookConfig/updateWebhookConfig helpers plus mock state.
Frontend — Icons
frontend/src/components/workflow/WorkflowIcons.tsx
Removes exported CloseIcon component and its JSX.
Server — Types
server/types/workflowTypes.ts
Adds new enum member WEBHOOK = "webhook" to ToolType.
Server — Webhook API & Router
server/api/webhook.ts
Adds webhook router and handler: registration/unregistration/listing, request handler handleWebhookRequest, execution triggering, validation, testing, and status endpoints. Exports webhookRouter and handleWebhookRequest.
Server — Workflow integration
server/api/workflow.ts
Hooks to reload/register webhooks when creating/updating templates/tools; registers/updates webhook registry on tool create/update.
Server — Registry & Reload
server/services/webhookRegistry.ts, server/services/webhookReloadService.ts
Adds webhookRegistry singleton (register/unregister/get/list), WebhookConfig type, and reload utility triggerWebhookReload + hasWebhookTools.
Server — Auth & Execution services
server/services/webhookAuthService.ts, server/services/webhookExecutionService.ts, server/services/webhookIntegrationService.ts
Adds WebhookAuthService (validation, headers, signatures, auditing), WebhookExecutionService (create/manage executions, status), and WebhookIntegrationService (create/update/delete webhook tools, register existing tools).
Server — Runtime & scripts
server/server.ts, server/manual-webhook-register.ts, server/register-webhook.ts
Adds runtime webhook handling in server bootstrap (load registry, /webhook/* handler, reload/list endpoints) and two registration helper scripts.

Sequence Diagram(s)

sequenceDiagram
  actor User
  participant TriggersSidebar
  participant WorkflowBuilder
  participant Canvas
  participant WebhookConfigUI as "Webhook Configuration UI"
  participant credentialsAPI as "credentialsAPI / backend"

  User->>TriggersSidebar: Select "On Webhook Call"
  TriggersSidebar->>WorkflowBuilder: triggerSelected("webhook")
  WorkflowBuilder->>Canvas: create pending webhook node (temp id)
  WorkflowBuilder->>WebhookConfigUI: open with initialConfig/toolData
  WebhookConfigUI->>credentialsAPI: fetch credential list (if auth)
  User->>WebhookConfigUI: edit path/method/auth/headers/query
  WebhookConfigUI-->>User: show generated webhook URL (copyable)
  User->>WebhookConfigUI: Save
  WebhookConfigUI->>WorkflowBuilder: onSave(finalConfig)
  alt Creating new webhook
    WorkflowBuilder->>Canvas: replace pending node with webhook node (tool type "webhook")
    WorkflowBuilder->>credentialsAPI: saveWebhookConfig(...)
  else Updating existing webhook
    WorkflowBuilder->>Canvas: update node.tool/config
    WorkflowBuilder->>credentialsAPI: updateWebhookConfig(...)
  end
  WorkflowBuilder->>WebhookConfigUI: close UI, reset state
Loading
sequenceDiagram
  actor ExternalCaller
  participant HTTP as "Server /webhook/*"
  participant webhookRegistry
  participant WebhookAuthService
  participant WebhookExecutionService
  participant WorkflowEngine

  ExternalCaller->>HTTP: POST /webhook/<path> (headers/body)
  HTTP->>webhookRegistry: getWebhook(path)
  alt webhook found
    HTTP->>WebhookAuthService: validateAuthentication(request, config)
    WebhookAuthService-->>HTTP: ok / throw
    HTTP->>WebhookExecutionService: executeWorkflowFromWebhook(context)
    WebhookExecutionService->>WorkflowEngine: create execution & start async
    alt responseMode == immediate
      HTTP-->>ExternalCaller: 200 accepted (executionId)
    else responseMode == wait_for_completion
      WebhookExecutionService->>WebhookExecutionService: waitForExecution (poll)
      WebhookExecutionService-->>HTTP: final result or timeout
      HTTP-->>ExternalCaller: 200/504
    end
  else
    HTTP-->>ExternalCaller: 404
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • zereraz
  • shivamashtikar
  • kalpadhwaryu
  • junaid-shirur
  • devesh-juspay

Poem

I’m a rabbit wiring hooks so fine,
Paths and headers, URLs that shine.
I hop, I copy, a click — all set,
Nodes awaken where webhooks met.
Carrots tucked, credentials snug in line. 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 72.22% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title Check ❓ Inconclusive The title uses a conventional commit format with the scope “webhook” which correctly indicates the feature area but “node integration” is opaque and does not clearly reflect the broad set of frontend and backend changes introduced. The pull request implements new UI components, API handlers, server endpoints, and services for webhook support, which is not conveyed by the phrase “node integration”. As a result, the title is only partially related and too generic to convey the main change to other developers. Please update the title to more precisely summarize the primary change, for example “feat(webhook): add end-to-end webhook support with UI configuration and server endpoints”. A clearer title will help team members quickly understand the feature scope when reviewing commit history.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch webhook-node-integration

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @debojyoti-git, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces comprehensive webhook integration into the frontend workflow builder. It provides users with the ability to define custom webhook endpoints that can trigger workflows, offering granular control over HTTP methods, paths, authentication mechanisms, and response handling. The changes include a new configuration interface, a dedicated UI panel for setup, and seamless integration within the existing workflow visualization and editing environment.

Highlights

  • New Webhook Configuration Interface: Introduced WebhookConfig in Types.ts to define the structure for webhook properties like URL, HTTP method, path, authentication, and response mode.
  • Dedicated Webhook Configuration UI: Added a new React component, WebhookConfigurationUI.tsx, providing a user interface to set up and manage webhook details, including headers and query parameters.
  • Workflow Builder Integration: Incorporated webhook functionality into WorkflowBuilder.tsx, enabling users to add "On Webhook Call" as a trigger, visualize webhook nodes, and configure them directly within the workflow editor.
  • Dynamic Webhook Node Rendering: Implemented specific rendering logic for webhook nodes in StepNode, displaying relevant configuration details such as HTTP method, URL, and authentication type.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces webhook integration for workflows, including a new UI for webhook configuration and updates to the workflow builder to support webhook trigger nodes. The implementation is solid, but there are several opportunities for improvement regarding code duplication, maintainability, and user experience. I've provided suggestions to refactor complex logic, centralize duplicated code, replace legacy UI patterns, and improve data model consistency.

// Build description from webhook configuration
if (webhookConfig?.webhookUrl || webhookConfig?.path) {
const method = webhookConfig?.httpMethod || 'POST'
const url = webhookConfig?.webhookUrl || `${window.location.origin}/webhook${webhookConfig?.path || ''}`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The logic for generating the webhook URL is duplicated from the WebhookConfigurationUI component. Code duplication, especially for business logic like URL generation, is risky and can lead to inconsistencies if one location is updated and the other is not. This logic should be extracted into a shared utility function to ensure a single source of truth.

Comment on lines 47 to 95
useEffect(() => {
if (initialConfig) {
setConfig(initialConfig)
} else if (toolData?.val || toolData?.config) {
const data = toolData.val || toolData.config
setConfig({
webhookUrl: data.webhookUrl || "",
httpMethod: data.httpMethod || "POST",
path: data.path || "",
authentication: data.authentication || "none",
responseMode: data.responseMode || "immediately",
options: data.options || {},
headers: data.headers || {},
queryParams: data.queryParams || {},
})
}
}, [initialConfig, toolData])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The logic inside this useEffect to initialize the component's state is verbose, with the else if block repeating many default values. This can be refactored to be more concise and maintainable by consolidating the data source and then applying defaults, which reduces code repetition.

  useEffect(() => {
    const data = initialConfig || toolData?.val || toolData?.config
    if (data) {
      setConfig({
        webhookUrl: data.webhookUrl || "",
        httpMethod: data.httpMethod || "POST",
        path: data.path || "",
        authentication: data.authentication || "none",
        responseMode: data.responseMode || "immediately",
        options: data.options || {},
        headers: data.headers || {},
        queryParams: data.queryParams || {},
      })
    }
  }, [initialConfig, toolData])

Comment on lines 73 to 75
if (!config.path.trim()) {
alert("Please enter a webhook path")
return
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using alert() for form validation provides a poor user experience as it is a blocking and disruptive browser dialog. It's recommended to use non-blocking UI feedback, such as a snackbar/toast notification or an inline error message near the input field, to inform the user about the missing path.

Comment on lines 111 to 174
const copyWebhookUrl = async () => {
const url = config.path ? generateWebhookUrl() : `${window.location.origin}/webhook/your-path`
try {
await navigator.clipboard.writeText(url)
setCopied(true)
setTimeout(() => setCopied(false), 2000) // Reset after 2 seconds
} catch (err) {
console.error('Failed to copy URL:', err)
// Fallback for browsers that don't support clipboard API
const textArea = document.createElement('textarea')
textArea.value = url
document.body.appendChild(textArea)
textArea.select()
document.execCommand('copy')
document.body.removeChild(textArea)
setCopied(true)
setTimeout(() => setCopied(false), 2000)
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The logic for indicating a successful copy operation (setCopied(true) and the setTimeout) is duplicated in both the try block for the modern Clipboard API and the catch block for the fallback mechanism. This duplication can be avoided by extracting the success handling logic into a separate function, which would improve maintainability.

  // Copy webhook URL to clipboard
  const handleCopySuccess = () => {
    setCopied(true);
    setTimeout(() => setCopied(false), 2000); // Reset after 2 seconds
  };

  const copyWebhookUrl = async () => {
    const url = config.path ? generateWebhookUrl() : `${window.location.origin}/webhook/your-path`;
    try {
      await navigator.clipboard.writeText(url);
      handleCopySuccess();
    } catch (err) {
      console.error('Failed to copy URL:', err);
      // Fallback for browsers that don't support clipboard API
      const textArea = document.createElement('textarea');
      textArea.value = url;
      document.body.appendChild(textArea);
      textArea.select();
      document.execCommand('copy');
      document.body.removeChild(textArea);
      handleCopySuccess();
    }
  }

Comment on lines +162 to +214
style={{
display: "flex",
padding: "20px",
alignItems: "center",
gap: "10px",
alignSelf: "stretch",
borderBottom: "1px solid var(--gray-300, #E4E6E7)",
}}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This component uses several inline style objects for styling. Since the project is already configured with Tailwind CSS, it's a best practice to replace these inline styles with Tailwind's utility classes. This improves code consistency, makes it easier to manage styles, and ensures that theming (like dark mode) is applied correctly.

Comment on lines +798 to +812
{(() => {
// First try to get title from workflow_tools[index].val.title
if (hasWebhookTool && tools?.[0]?.val && typeof tools[0].val === 'object' && (tools[0].val as any)?.title) {
return (tools[0].val as any).title
}

// Try to get title from workflow_tools[index].value.title
if (hasWebhookTool && tools?.[0] && (tools[0] as any)?.value && typeof (tools[0] as any).value === 'object' && (tools[0] as any).value?.title) {
return (tools[0] as any).value.title
}

// Fallback to "Webhook" title
return "Webhook"
})()}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This IIFE (Immediately Invoked Function Expression) to determine the webhook title contains complex logic that is repeated for determining the description. This pattern reduces readability and maintainability. It would be cleaner to extract this logic into a dedicated helper function. A similar function could be created for the description as well.

Comment on lines +843 to +847
const auth = webhookConfig?.authentication === 'none' ? 'No authentication' :
webhookConfig?.authentication === 'basic' ? 'Basic authentication' :
webhookConfig?.authentication === 'bearer' ? 'Bearer token authentication' :
webhookConfig?.authentication === 'api_key' ? 'API key authentication' : 'No authentication'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This long chain of ternary operators for generating the authentication description is hard to read and maintain. A mapping object would be a much cleaner and more scalable solution.

                  const authMap: Record<string, string> = {
                    'none': 'No authentication',
                    'basic': 'Basic authentication',
                    'bearer': 'Bearer token authentication',
                    'api_key': 'API key authentication',
                  };
                  const auth = authMap[webhookConfig?.authentication] ?? 'No authentication';

Comment on lines 3463 to 3469
const webhookTool = {
id: `tool-${newNodeId}`,
type: "webhook",
val: webhookConfig, // Use 'val' to match template structure
value: webhookConfig, // Also include 'value' for compatibility
config: webhookConfig,
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The webhookTool object is being created with val, value, and config properties, all holding the same webhookConfig data. This redundancy can lead to confusion and makes the data model harder to manage. It's best to have a single source of truth for the tool's configuration. This change might require adjustments in other parts of the code that consume this tool object, but it will lead to a more robust and clearer data model.

        const webhookTool = {
          id: `tool-${newNodeId}`,
          type: "webhook",
          config: webhookConfig,
        }

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
frontend/src/components/workflow/WorkflowBuilder.tsx (1)

2328-2358: Include webhook nodes in the “valid trigger” check.

hasValidTrigger never turns true for webhook steps, so workflows that start with the new webhook trigger can’t be saved—the Save button stays disabled forever. Add the "webhook" type to this guard.

-        return nodeData?.step?.type && 
-               nodeData.step.type !== "trigger_selector" && 
-               (nodeData.step.type === "form_submission" || 
-                nodeData.step.type === "manual" || 
-                nodeData.step.type === "schedule" ||
-                nodeData.step.type === "app_event")
+        return (
+          nodeData?.step?.type &&
+          nodeData.step.type !== "trigger_selector" &&
+          [
+            "form_submission",
+            "manual",
+            "schedule",
+            "app_event",
+            "webhook",
+          ].includes(nodeData.step.type)
+        )
🧹 Nitpick comments (1)
frontend/src/components/workflow/Types.ts (1)

303-312: Clarify optionality for webhook authentication settings.

Currently authentication is always required, yet method-specific fields (e.g., auth details for basic, bearer, API key) are undocumented and absent. If the UI can submit “none” without any additional payload, consider either:

  • Making authentication optional (defaulting to "none"), or
  • Introducing dedicated option fields (e.g., basicAuth?: { username: string; password: string }) so downstream consumers know what to expect.

Without this refinement, implementers must inspect the UI to guess the payload shape, increasing integration risk. Tightening the type here keeps the contract self-describing.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 987adbd and 19dc357.

📒 Files selected for processing (4)
  • frontend/src/components/workflow/Types.ts (1 hunks)
  • frontend/src/components/workflow/WebhookConfigurationUI.tsx (1 hunks)
  • frontend/src/components/workflow/WorkflowBuilder.tsx (16 hunks)
  • frontend/src/components/workflow/WorkflowIcons.tsx (0 hunks)
💤 Files with no reviewable changes (1)
  • frontend/src/components/workflow/WorkflowIcons.tsx
🧰 Additional context used
🧬 Code graph analysis (3)
frontend/src/components/workflow/Types.ts (1)
frontend/src/components/workflow/WebhookConfigurationUI.tsx (1)
  • WebhookConfig (486-486)
frontend/src/components/workflow/WebhookConfigurationUI.tsx (2)
frontend/src/components/workflow/Types.ts (1)
  • WebhookConfig (303-312)
frontend/src/components/workflow/WorkflowIcons.tsx (1)
  • BackArrowIcon (393-413)
frontend/src/components/workflow/WorkflowBuilder.tsx (3)
frontend/src/components/workflow/WorkflowIcons.tsx (1)
  • WebhookIcon (166-184)
frontend/src/components/workflow/Types.ts (2)
  • WebhookConfig (303-312)
  • Tool (56-61)
frontend/src/components/workflow/WebhookConfigurationUI.tsx (3)
  • WebhookConfig (486-486)
  • WebhookConfigurationUI (19-483)
  • WebhookConfigurationUI (485-485)

Comment on lines 66 to 113
const generateWebhookUrl = () => {
const baseUrl = window.location.origin
const cleanPath = config.path.startsWith('/') ? config.path : `/${config.path}`
return `${baseUrl}/webhook${cleanPath}`
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Guard browser globals during render.

Referencing window.location while rendering will throw in SSR/Prerender environments, making this panel unusable there. Please gate the access behind a runtime check and reuse the helper everywhere you build the URL.

+  const getOrigin = () =>
+    typeof window !== "undefined" ? window.location.origin : ""
+
   // Generate webhook URL based on the path
   const generateWebhookUrl = () => {
-    const baseUrl = window.location.origin
+    const baseUrl = getOrigin()
     const cleanPath = config.path.startsWith('/') ? config.path : `/${config.path}`
     return `${baseUrl}/webhook${cleanPath}`
   }
…
-                    {config.path ? generateWebhookUrl() : `${window.location.origin}/webhook/your-path`}
+                    {config.path ? generateWebhookUrl() : `${getOrigin()}/webhook/your-path`}

Also applies to: 228-231

🤖 Prompt for AI Agents
In frontend/src/components/workflow/WebhookConfigurationUI.tsx around lines
66-70 (and also apply to lines 228-231), the code reads window.location.origin
directly which will throw during SSR/prerender; replace this with a small
runtime-safe helper that first checks typeof window !== "undefined" before
accessing window.location, returning a sensible fallback (e.g. empty string or a
configurable baseUrl) when window is unavailable, and then use that helper
everywhere the webhook URL is built so both locations reuse the same safe logic.

Comment on lines +842 to +850
const url = webhookConfig?.webhookUrl || `${window.location.origin}/webhook${webhookConfig?.path || ''}`
const auth = webhookConfig?.authentication === 'none' ? 'No authentication' :
webhookConfig?.authentication === 'basic' ? 'Basic authentication' :
webhookConfig?.authentication === 'bearer' ? 'Bearer token authentication' :
webhookConfig?.authentication === 'api_key' ? 'API key authentication' : 'No authentication'

return `${method} ${url}${auth}`
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Guard window usage in node rendering.

This JSX executes during render; on an SSR pass it will hit window and crash. Please mirror the getOrigin() helper (or inline the guard) before constructing the fallback URL.

-                  const url = webhookConfig?.webhookUrl || `${window.location.origin}/webhook${webhookConfig?.path || ''}`
+                  const origin =
+                    typeof window !== "undefined" ? window.location.origin : ""
+                  const url =
+                    webhookConfig?.webhookUrl ||
+                    `${origin}/webhook${webhookConfig?.path || ""}`
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const url = webhookConfig?.webhookUrl || `${window.location.origin}/webhook${webhookConfig?.path || ''}`
const auth = webhookConfig?.authentication === 'none' ? 'No authentication' :
webhookConfig?.authentication === 'basic' ? 'Basic authentication' :
webhookConfig?.authentication === 'bearer' ? 'Bearer token authentication' :
webhookConfig?.authentication === 'api_key' ? 'API key authentication' : 'No authentication'
return `${method} ${url}${auth}`
}
const origin =
typeof window !== "undefined" ? window.location.origin : ""
const url =
webhookConfig?.webhookUrl ||
`${origin}/webhook${webhookConfig?.path || ""}`
const auth =
webhookConfig?.authentication === "none"
? "No authentication"
: webhookConfig?.authentication === "basic"
? "Basic authentication"
: webhookConfig?.authentication === "bearer"
? "Bearer token authentication"
: webhookConfig?.authentication === "api_key"
? "API key authentication"
: "No authentication"
return `${method} ${url}${auth}`
}
🤖 Prompt for AI Agents
In frontend/src/components/workflow/WorkflowBuilder.tsx around lines 842 to 849,
the render constructs a fallback URL using window.location.origin which will
crash during SSR; update the code to guard window access by using the existing
getOrigin() helper (or inline a check like typeof window !== 'undefined' ?
window.location.origin : '') before building the fallback URL, so replace
`${window.location.origin}/webhook...` with a call to getOrigin() (or the
guarded expression) to ensure safe server-side rendering.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 12

🧹 Nitpick comments (12)
frontend/src/components/workflow/Types.ts (1)

303-313: Type looks fine; align casing and ID semantics across API

  • Confirm server expects camelCase (webhookUrl) vs snake_case (webhook_url) used elsewhere in ToolValue/ToolConfig.
  • Consider renaming selectedCredential → selectedCredentialId for clarity.
frontend/src/components/workflow/api/ApiHandlers.ts (3)

517-523: Avoid relying on this binding inside object literal

Using this.createTool can break if the method is detached. Call via the namespace.

Apply:

-      const response = await this.createTool(toolData)
+      const response = await workflowToolsAPI.createTool(toolData)

632-634: Avoid this binding in update; call via namespace

Apply:

-    const response = await this.updateTool(toolId, toolData)
+    const response = await workflowToolsAPI.updateTool(toolId, toolData)

438-546: Scrub verbose console logs (some contain or could contain sensitive config)

Drop or guard logs behind a debug flag. Avoid logging payloads entirely in production.

frontend/src/components/workflow/WebhookConfigurationUI.tsx (3)

334-343: Authentication options incomplete vs type.

WebhookConfig allows "bearer" and "api_key" but the dropdown exposes only "none" and "basic". Add the missing options for parity.

   <Dropdown
     options={[
       { value: "none", label: "None" },
-      { value: "basic", label: "Basic Auth" }
+      { value: "basic", label: "Basic Auth" },
+      { value: "bearer", label: "Bearer Token" },
+      { value: "api_key", label: "API Key" }
     ]}

112-116: Avoid blocking alert for validation.

Replace alert with inline error or a toast/snackbar for better UX.

I can wire a small pathError state and render a helper text under the input. Want a patch?


61-76: Remove or gate verbose console logs.

These logs will spam prod consoles. Wrap with env guard or drop.

-      console.log("🔧 Initializing webhook config from toolData:", { valueData, configData })
+      if (process.env.NODE_ENV !== "production") {
+        console.debug("🔧 Initializing webhook config from toolData:", { valueData, configData })
+      }-      console.log("🔧 Extracted path from toolData:", valueData.path)
+      if (process.env.NODE_ENV !== "production") {
+        console.debug("🔧 Extracted path from toolData:", valueData.path)
+      }-    console.log("🔧 Setting webhook config:", newConfig)
+    if (process.env.NODE_ENV !== "production") {
+      console.debug("🔧 Setting webhook config:", newConfig)
+    }

Also applies to: 91-91

frontend/src/components/CredentialModal.tsx (2)

58-61: Validate before save.

Prevent saving empty credentials (e.g., name/user/password) and signal errors inline.

I can add minimal required checks with helper texts and disabled Save until valid. Proceed?


76-94: Use unified Input component (optional).

For consistency with the rest of the UI, consider using the shared for the name field too.

frontend/src/components/workflow/WorkflowBuilder.tsx (3)

3480-3514: Use a unique node ID for the webhook trigger.

Hardcoding "webhook-trigger" risks collisions; prefer a counter- or timestamp-based ID.

-          const newNodeId = "webhook-trigger"
+          const newNodeId = `webhook-${nodeCounter}`

3580-3583: Replace alert with Snackbar for errors.

You already have showSnackbarMessage; use it for consistent UX.

-        alert("Failed to save webhook configuration. Please try again.")
+        showSnackbarMessage("Failed to save webhook configuration. Please try again.", "error")

159-160: Import type from Types to decouple UI.

Avoid importing WebhookConfig via the UI module; import the type directly from "./Types".

-import WebhookConfigurationUI, { WebhookConfig } from "./WebhookConfigurationUI"
+import WebhookConfigurationUI from "./WebhookConfigurationUI"
+import { WebhookConfig } from "./Types"
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 19dc357 and b364321.

📒 Files selected for processing (8)
  • frontend/src/components/CredentialExample.tsx (1 hunks)
  • frontend/src/components/CredentialModal.tsx (1 hunks)
  • frontend/src/components/workflow/CredentialSelector.tsx (1 hunks)
  • frontend/src/components/workflow/Types.ts (1 hunks)
  • frontend/src/components/workflow/WebhookConfigurationUI.tsx (1 hunks)
  • frontend/src/components/workflow/WorkflowBuilder.tsx (16 hunks)
  • frontend/src/components/workflow/api/ApiHandlers.ts (3 hunks)
  • server/types/workflowTypes.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (6)
frontend/src/components/workflow/CredentialSelector.tsx (2)
frontend/src/components/workflow/api/ApiHandlers.ts (2)
  • Credential (5-18)
  • credentialsAPI (703-841)
frontend/src/components/CredentialModal.tsx (2)
  • CredentialData (17-22)
  • CredentialModal (32-207)
frontend/src/components/CredentialExample.tsx (1)
frontend/src/components/CredentialModal.tsx (2)
  • CredentialData (17-22)
  • CredentialModal (32-207)
frontend/src/components/workflow/WorkflowBuilder.tsx (4)
frontend/src/components/workflow/WorkflowIcons.tsx (1)
  • WebhookIcon (166-184)
frontend/src/components/workflow/Types.ts (2)
  • WebhookConfig (303-313)
  • Tool (56-61)
frontend/src/components/workflow/WebhookConfigurationUI.tsx (3)
  • WebhookConfig (543-543)
  • WebhookConfigurationUI (20-540)
  • WebhookConfigurationUI (542-542)
frontend/src/components/workflow/api/ApiHandlers.ts (1)
  • workflowToolsAPI (392-635)
frontend/src/components/CredentialModal.tsx (4)
frontend/src/components/ui/dialog.tsx (2)
  • Dialog (109-109)
  • DialogContent (114-114)
frontend/src/components/ui/button.tsx (1)
  • Button (57-57)
frontend/src/components/ui/input.tsx (1)
  • Input (25-25)
frontend/src/components/ui/select.tsx (5)
  • Select (111-111)
  • SelectTrigger (114-114)
  • SelectValue (113-113)
  • SelectContent (115-115)
  • SelectItem (117-117)
frontend/src/components/workflow/WebhookConfigurationUI.tsx (3)
frontend/src/components/workflow/Types.ts (1)
  • WebhookConfig (303-313)
frontend/src/components/workflow/WorkflowIcons.tsx (1)
  • BackArrowIcon (393-413)
frontend/src/components/workflow/CredentialSelector.tsx (1)
  • CredentialSelector (13-305)
frontend/src/components/workflow/Types.ts (1)
frontend/src/components/workflow/WebhookConfigurationUI.tsx (1)
  • WebhookConfig (543-543)
🔇 Additional comments (6)
frontend/src/components/workflow/api/ApiHandlers.ts (1)

494-498: Verify server expects camelCase keys in tool.value

Existing types elsewhere include webhook_url; confirm server contracts.

frontend/src/components/workflow/CredentialSelector.tsx (1)

1-5: Imports and API usage look consistent

Component correctly leverages credentialsAPI and CredentialModal.

server/types/workflowTypes.ts (1)

24-25: WEBHOOK accepted by all validators and DB enums

  • pgEnum("tool_type", Object.values(ToolType)) in server/db/schema/workflows.ts now includes "webhook".
  • Zod schemas (z.enum(Object.values(ToolType))) automatically permit "webhook".
  • No hard-coded allowlists or manual checks found in server/api/workflow.ts.
frontend/src/components/workflow/WebhookConfigurationUI.tsx (2)

151-169: Deduplicate copy success handling.

Extract the setCopied/setTimeout logic and reuse in both paths.

Apply this diff:

   const copyWebhookUrl = async () => {
     const url = config.path ? generateWebhookUrl() : `${getOrigin()}/webhook/your-path`
     try {
       await navigator.clipboard.writeText(url)
-      setCopied(true)
-      setTimeout(() => setCopied(false), 2000) // Reset after 2 seconds
+      handleCopySuccess()
     } catch (err) {
       console.error('Failed to copy URL:', err)
       // Fallback for browsers that don't support clipboard API
       const textArea = document.createElement('textarea')
       textArea.value = url
       document.body.appendChild(textArea)
       textArea.select()
       document.execCommand('copy')
       document.body.removeChild(textArea)
-      setCopied(true)
-      setTimeout(() => setCopied(false), 2000)
+      handleCopySuccess()
     }
   }

Add this helper (outside the selected range, e.g., above copyWebhookUrl):

const handleCopySuccess = () => {
  setCopied(true)
  setTimeout(() => setCopied(false), 2000)
}

201-223: Inline styles: prefer Tailwind utilities for consistency.

The project uses Tailwind; convert these inline style objects to utility classes.

Also applies to: 228-238, 244-251

frontend/src/components/workflow/WorkflowBuilder.tsx (1)

843-849: SSR crash: window used during render.

This executes while rendering StepNode; window is undefined in SSR.

-                  const url = webhookConfig?.webhookUrl || `${window.location.origin}/webhook${webhookConfig?.path || ''}`
+                  const origin =
+                    typeof window !== "undefined" ? window.location.origin : ""
+                  const url =
+                    webhookConfig?.webhookUrl ||
+                    `${origin}/webhook${webhookConfig?.path || ""}`

Consider extracting a shared getOrigin()/buildWebhookUrl() util to de-duplicate across components.

Comment on lines +8 to +10
const handleCredentialSave = (data: CredentialData) => {
console.log("Credential saved:", data)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Do not log secrets

This logs user/password from the modal. Remove or redact before merging.

Apply:

-  const handleCredentialSave = (data: CredentialData) => {
-    console.log("Credential saved:", data)
-  }
+  const handleCredentialSave = (_data: CredentialData) => {
+    // Persist securely; avoid logging secrets
+  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleCredentialSave = (data: CredentialData) => {
console.log("Credential saved:", data)
}
const handleCredentialSave = (_data: CredentialData) => {
// Persist securely; avoid logging secrets
}
🤖 Prompt for AI Agents
In frontend/src/components/CredentialExample.tsx around lines 8 to 10, the
handler currently logs full credential objects (including username and
password); remove the console.log of the raw data and either omit sensitive
fields or redact them before logging (e.g., replace password and any secret with
"***" or log only non-sensitive metadata), or better yet emit a non-logging
confirmation or use a safe telemetry event that excludes credentials; update the
handler to never print user/password to console or logs.

Comment on lines +441 to +487
// Format credential data for backend storage
const formatCredentialData = async () => {
console.log("🔐 Formatting credential data...")
if (webhookConfig.authentication === "none") {
console.log("ℹ️ No authentication, returning empty array")
return []
}

try {
// Fetch all credentials of the required type
console.log("🔍 Fetching all credentials for auth type:", webhookConfig.authentication)
const allCredentials = await credentialsAPI.fetchByType(webhookConfig.authentication as "basic" | "bearer" | "api_key")

if (allCredentials.length === 0) {
console.log("ℹ️ No credentials found for this auth type")
return []
}

console.log(`✅ Found ${allCredentials.length} credentials of type ${webhookConfig.authentication}`)

// Map all credentials with isSelected flag based on selectedCredential
const credentialData = allCredentials.map(credential => {
const isSelected = credential.id === webhookConfig.selectedCredential

// Create base64 encoding of user:password for basic auth
const basicAuth = btoa(`${credential.user}:${credential.password}`)

console.log(`📋 Processing credential: ${credential.name}, isSelected: ${isSelected}`)

return {
user: credential.user,
password: credential.password, // Note: In production, this should be encrypted
basic_auth: basicAuth,
isSelected,
name: credential.name,
allowedDomains: credential.allowedDomains
}
})

console.log("📦 Formatted credential data with all credentials:", credentialData)
return credentialData
} catch (error) {
console.error("❌ Error formatting credential data:", error)
return []
}
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: Frontend collects and prepares to send secrets (user/password/base64) — remove immediately

Never assemble or transmit credential secrets from the client. Persist only a reference (credential ID); resolve secrets server-side.

Apply:

-    // Format credential data for backend storage
-    const formatCredentialData = async () => {
-      console.log("🔐 Formatting credential data...")
-      if (webhookConfig.authentication === "none") {
-        console.log("ℹ️ No authentication, returning empty array")
-        return []
-      }
-
-      try {
-        // Fetch all credentials of the required type
-        console.log("🔍 Fetching all credentials for auth type:", webhookConfig.authentication)
-        const allCredentials = await credentialsAPI.fetchByType(webhookConfig.authentication as "basic" | "bearer" | "api_key")
-        
-        if (allCredentials.length === 0) {
-          console.log("ℹ️ No credentials found for this auth type")
-          return []
-        }
-
-        console.log(`✅ Found ${allCredentials.length} credentials of type ${webhookConfig.authentication}`)
-
-        // Map all credentials with isSelected flag based on selectedCredential
-        const credentialData = allCredentials.map(credential => {
-          const isSelected = credential.id === webhookConfig.selectedCredential
-          
-          // Create base64 encoding of user:password for basic auth
-          const basicAuth = btoa(`${credential.user}:${credential.password}`)
-          
-          console.log(`📋 Processing credential: ${credential.name}, isSelected: ${isSelected}`)
-          
-          return {
-            user: credential.user,
-            password: credential.password, // Note: In production, this should be encrypted
-            basic_auth: basicAuth,
-            isSelected,
-            name: credential.name,
-            allowedDomains: credential.allowedDomains
-          }
-        })
-
-        console.log("📦 Formatted credential data with all credentials:", credentialData)
-        return credentialData
-      } catch (error) {
-        console.error("❌ Error formatting credential data:", error)
-        return []
-      }
-    }
+    // Persist only a reference to the selected credential; secrets must be resolved server-side
+    const credentialRef = webhookConfig.selectedCredential ?? null

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In frontend/src/components/workflow/api/ApiHandlers.ts around lines 441 to 487,
the client-side code is assembling and potentially sending credential secrets
(user/password/base64); remove that behavior and only return credential
references: map fetched credentials to an object containing the credential id
and selection flag (e.g., { id, isSelected }) and optionally non-secret metadata
like name, and remove any user, password, basic_auth or base64 logic; update any
callers to expect IDs instead and ensure secret resolution (fetching actual
user/password) is performed on the server side only via a secure backend
endpoint.

}
}

const credentialArray = await formatCredentialData()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Remove secret array generation

Stop awaiting and using the formatted secret array.

Apply:

-    const credentialArray = await formatCredentialData()
+    // No secret material derived on the client
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const credentialArray = await formatCredentialData()
// No secret material derived on the client
🤖 Prompt for AI Agents
In frontend/src/components/workflow/api/ApiHandlers.ts around line 488, remove
the awaited assignment to credentialArray and stop using that formatted secret
array; either delete the line "const credentialArray = await
formatCredentialData()" and any subsequent references to credentialArray, or if
formatCredentialData must run for side-effects call it without awaiting (e.g.,
formatCredentialData()) and remove any code that reads from credentialArray.

Comment on lines +499 to +501
description: `${webhookConfig.httpMethod} ${webhookConfig.webhookUrl}${
webhookConfig.authentication === 'none' ? 'No authentication' : 'Basic authentication'
}`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix auth description for non-basic types

Current text implies all non-none auth is “Basic”. Handle bearer/api_key.

Apply:

-        description: `${webhookConfig.httpMethod} ${webhookConfig.webhookUrl} • ${
-          webhookConfig.authentication === 'none' ? 'No authentication' : 'Basic authentication'
-        }`
+        description: `${webhookConfig.httpMethod} ${webhookConfig.webhookUrl} • ${
+          webhookConfig.authentication === 'none'
+            ? 'No authentication'
+            : webhookConfig.authentication === 'basic'
+              ? 'Basic authentication'
+              : webhookConfig.authentication === 'bearer'
+                ? 'Bearer token'
+                : 'API key'
+        }`
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
description: `${webhookConfig.httpMethod} ${webhookConfig.webhookUrl} • ${
webhookConfig.authentication === 'none' ? 'No authentication' : 'Basic authentication'
}`
description: `${webhookConfig.httpMethod} ${webhookConfig.webhookUrl} • ${
webhookConfig.authentication === 'none'
? 'No authentication'
: webhookConfig.authentication === 'basic'
? 'Basic authentication'
: webhookConfig.authentication === 'bearer'
? 'Bearer token'
: 'API key'
}`
🤖 Prompt for AI Agents
In frontend/src/components/workflow/api/ApiHandlers.ts around lines 499 to 501,
the description string currently treats any non-'none' authentication as "Basic
authentication"; update the interpolation to branch on
webhookConfig.authentication and produce correct text for each type (e.g., 'No
authentication' for 'none', 'Basic authentication' for 'basic', 'Bearer token
authentication' for 'bearer', 'API key authentication' for 'api_key', and a
sensible fallback like 'Authentication configured' for unknown values) so the
displayed description accurately reflects the auth method.

Comment on lines +503 to +513
config: {
// Store behavior configuration in config column
authentication: webhookConfig.authentication,
responseMode: webhookConfig.responseMode,
headers: webhookConfig.headers || {},
queryParams: webhookConfig.queryParams || {},
options: webhookConfig.options || {},
credentials: credentialArray, // Array of credential objects with base64 auth
selectedCredential: webhookConfig.selectedCredential
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Do not include credentials array in config

Remove credentials from payload; pass only selectedCredential reference.

Apply:

       config: {
         // Store behavior configuration in config column
         authentication: webhookConfig.authentication,
         responseMode: webhookConfig.responseMode,
         headers: webhookConfig.headers || {},
         queryParams: webhookConfig.queryParams || {},
-        options: webhookConfig.options || {},
-        credentials: credentialArray, // Array of credential objects with base64 auth
-        selectedCredential: webhookConfig.selectedCredential
+        options: webhookConfig.options || {},
+        selectedCredential: credentialRef
       }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
config: {
// Store behavior configuration in config column
authentication: webhookConfig.authentication,
responseMode: webhookConfig.responseMode,
headers: webhookConfig.headers || {},
queryParams: webhookConfig.queryParams || {},
options: webhookConfig.options || {},
credentials: credentialArray, // Array of credential objects with base64 auth
selectedCredential: webhookConfig.selectedCredential
}
}
config: {
// Store behavior configuration in config column
authentication: webhookConfig.authentication,
responseMode: webhookConfig.responseMode,
headers: webhookConfig.headers || {},
queryParams: webhookConfig.queryParams || {},
options: webhookConfig.options || {},
selectedCredential: credentialRef
}
}
🤖 Prompt for AI Agents
In frontend/src/components/workflow/api/ApiHandlers.ts around lines 503 to 513,
the config payload currently includes a credentials array (credentialArray)
which must be removed; update the config object to not send credentials and
instead only include selectedCredential (webhookConfig.selectedCredential).
Remove any reference to credentialArray from this payload construction and
ensure downstream callers or API accepts only the selectedCredential identifier;
if credentialArray is used elsewhere, keep it locally but do not serialize it
into config.

Comment on lines +616 to +618
description: `${webhookConfig.httpMethod} ${webhookConfig.webhookUrl}${
webhookConfig.authentication === 'none' ? 'No authentication' : 'Basic authentication'
}`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix auth description in update flow

Apply:

-        description: `${webhookConfig.httpMethod} ${webhookConfig.webhookUrl} • ${
-          webhookConfig.authentication === 'none' ? 'No authentication' : 'Basic authentication'
-        }`
+        description: `${webhookConfig.httpMethod} ${webhookConfig.webhookUrl} • ${
+          webhookConfig.authentication === 'none'
+            ? 'No authentication'
+            : webhookConfig.authentication === 'basic'
+              ? 'Basic authentication'
+              : webhookConfig.authentication === 'bearer'
+                ? 'Bearer token'
+                : 'API key'
+        }`
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
description: `${webhookConfig.httpMethod} ${webhookConfig.webhookUrl} • ${
webhookConfig.authentication === 'none' ? 'No authentication' : 'Basic authentication'
}`
description: `${webhookConfig.httpMethod} ${webhookConfig.webhookUrl} • ${
webhookConfig.authentication === 'none'
? 'No authentication'
: webhookConfig.authentication === 'basic'
? 'Basic authentication'
: webhookConfig.authentication === 'bearer'
? 'Bearer token'
: 'API key'
}`
🤖 Prompt for AI Agents
In frontend/src/components/workflow/api/ApiHandlers.ts around lines 616-618, the
description text hardcodes "Basic authentication" for any non-'none' auth;
change it to reflect the actual webhookConfig.authentication value (e.g., map
'basic' -> 'Basic authentication', 'bearer' or 'token' -> 'Bearer
authentication', etc.) instead of always showing Basic, and handle undefined
values by defaulting to a readable label (or "Unknown authentication"); update
the template string to use that mapped label.

Comment on lines +621 to +629
// Store behavior configuration in config column
authentication: webhookConfig.authentication,
responseMode: webhookConfig.responseMode,
headers: webhookConfig.headers || {},
queryParams: webhookConfig.queryParams || {},
options: webhookConfig.options || {},
credentials: credentialArray, // Array of credential objects with base64 auth
selectedCredential: webhookConfig.selectedCredential
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Do not include credentials array in update payload

Apply:

         headers: webhookConfig.headers || {},
         queryParams: webhookConfig.queryParams || {},
         options: webhookConfig.options || {},
-        credentials: credentialArray, // Array of credential objects with base64 auth
-        selectedCredential: webhookConfig.selectedCredential
+        selectedCredential: credentialRef
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Store behavior configuration in config column
authentication: webhookConfig.authentication,
responseMode: webhookConfig.responseMode,
headers: webhookConfig.headers || {},
queryParams: webhookConfig.queryParams || {},
options: webhookConfig.options || {},
credentials: credentialArray, // Array of credential objects with base64 auth
selectedCredential: webhookConfig.selectedCredential
}
// Store behavior configuration in config column
authentication: webhookConfig.authentication,
responseMode: webhookConfig.responseMode,
headers: webhookConfig.headers || {},
queryParams: webhookConfig.queryParams || {},
options: webhookConfig.options || {},
selectedCredential: credentialRef
}
🤖 Prompt for AI Agents
In frontend/src/components/workflow/api/ApiHandlers.ts around lines 621 to 629,
the update payload is including the full credentials array (credentials:
credentialArray) which must not be sent; remove the credentials field from the
object constructed for the update and only send non-sensitive fields (e.g.,
selectedCredential if needed), or explicitly delete/omit credentials before
making the API call so no credential objects or base64 auth data are
transmitted.

Comment on lines +117 to +123
const credential = await credentialsAPI.update(editingCredential.id, {
name: updatedCredential.name,
user: updatedCredential.user,
password: updatedCredential.password,
allowedDomains: updatedCredential.allowedDomains
})

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Avoid wiping password on edit when left blank

Only include password in update if the user provided one.

Apply:

-      const credential = await credentialsAPI.update(editingCredential.id, {
-        name: updatedCredential.name,
-        user: updatedCredential.user,
-        password: updatedCredential.password,
-        allowedDomains: updatedCredential.allowedDomains
-      })
+      const updates: Partial<Credential> = {
+        name: updatedCredential.name,
+        user: updatedCredential.user,
+        allowedDomains: updatedCredential.allowedDomains,
+      }
+      if (updatedCredential.password?.trim()) {
+        updates.password = updatedCredential.password
+      }
+      const credential = await credentialsAPI.update(editingCredential.id, updates)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const credential = await credentialsAPI.update(editingCredential.id, {
name: updatedCredential.name,
user: updatedCredential.user,
password: updatedCredential.password,
allowedDomains: updatedCredential.allowedDomains
})
const updates: Partial<Credential> = {
name: updatedCredential.name,
user: updatedCredential.user,
allowedDomains: updatedCredential.allowedDomains,
}
if (updatedCredential.password?.trim()) {
updates.password = updatedCredential.password
}
const credential = await credentialsAPI.update(editingCredential.id, updates)
🤖 Prompt for AI Agents
In frontend/src/components/workflow/CredentialSelector.tsx around lines 117 to
123, the current update call always sends password which wipes it when the field
is left blank; change to build the update payload dynamically (e.g., create an
object with name, user, allowedDomains) and only add the password property if
updatedCredential.password is non-empty/defined before calling
credentialsAPI.update so empty input does not overwrite the stored password.

Comment on lines 105 to 113
// Generate webhook URL based on the path
const generateWebhookUrl = () => {
const baseUrl = window.location.origin
const cleanPath = config.path?.startsWith('/') ? config.path : `/${config.path || ''}`
return `${baseUrl}/webhook${cleanPath}`
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Guard window during URL generation (SSR-safe).

Accessing window.location.origin will crash on SSR. Use a small helper and reuse it.

Apply this diff:

   const generateWebhookUrl = () => {
-    const baseUrl = window.location.origin
+    const baseUrl = getOrigin()
     const cleanPath = config.path?.startsWith('/') ? config.path : `/${config.path || ''}`
     return `${baseUrl}/webhook${cleanPath}`
   }

Add this helper (outside the selected range, e.g., above generateWebhookUrl):

const getOrigin = () =>
  typeof window !== "undefined" && window.location?.origin ? window.location.origin : ""
🤖 Prompt for AI Agents
In frontend/src/components/workflow/WebhookConfigurationUI.tsx around lines 105
to 110, the generateWebhookUrl function directly accesses window.location.origin
which will crash during SSR; add a small SSR-safe helper function (e.g.,
getOrigin) above generateWebhookUrl that returns window.location.origin only
when typeof window !== "undefined" and window.location?.origin exists, otherwise
an empty string, then refactor generateWebhookUrl to call getOrigin() instead of
directly using window.location.origin so URL generation is safe and reusable.

Comment on lines 151 to 158
const copyWebhookUrl = async () => {
const url = config.path ? generateWebhookUrl() : `${window.location.origin}/webhook/your-path`
try {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Guard window in copy/fallback and UI render.

Replace direct window.location.origin usages.

-    const url = config.path ? generateWebhookUrl() : `${window.location.origin}/webhook/your-path`
+    const url = config.path ? generateWebhookUrl() : `${getOrigin()}/webhook/your-path`
-                    {config.path ? generateWebhookUrl() : `${window.location.origin}/webhook/your-path`}
+                    {config.path ? generateWebhookUrl() : `${getOrigin()}/webhook/your-path`}

Also applies to: 269-269

🤖 Prompt for AI Agents
In frontend/src/components/workflow/WebhookConfigurationUI.tsx around lines
151-153 (and also at line 269), the code uses window.location.origin directly
for the copied URL and fallback UI rendering; guard against window being
undefined (SSR or non-browser env) by deriving origin in a safe way—use typeof
window !== 'undefined' && window.location?.origin ? window.location.origin : an
appropriate fallback (e.g., empty string or process.env.BASE_URL) and update
both copyWebhookUrl and the UI render to use that safe origin value so no
runtime error occurs during server-side rendering or tests.

@avirupsinha12 avirupsinha12 force-pushed the webhook-node-integration branch from b364321 to f5b2ae6 Compare October 9, 2025 08:45
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 15

♻️ Duplicate comments (11)
frontend/src/components/CredentialExample.tsx (1)

8-10: Remove credential logging (previously raised).

This logs sensitive credential data including passwords to the console, which is a security risk.

As noted in the previous review, remove or redact sensitive fields before logging:

- const handleCredentialSave = (data: CredentialData) => {
-   console.log("Credential saved:", data)
- }
+ const handleCredentialSave = (_data: CredentialData) => {
+   // Persist securely; avoid logging secrets
+ }
frontend/src/components/workflow/CredentialSelector.tsx (1)

113-123: Avoid wiping password on edit when left blank

Build the update payload dynamically; only include password if provided.

-      const credential = await credentialsAPI.update(editingCredential.id, {
-        name: updatedCredential.name,
-        user: updatedCredential.user,
-        password: updatedCredential.password,
-        allowedDomains: updatedCredential.allowedDomains
-      })
+      const updates: Partial<Credential> = {
+        name: updatedCredential.name,
+        user: updatedCredential.user,
+        allowedDomains: updatedCredential.allowedDomains,
+      }
+      if (updatedCredential.password?.trim()) {
+        updates.password = updatedCredential.password
+      }
+      const credential = await credentialsAPI.update(editingCredential.id, updates)
frontend/src/components/workflow/api/ApiHandlers.ts (8)

488-488: Remove unused secret array variable

Eliminate the credentialArray usage entirely.

-    const credentialArray = await formatCredentialData()
+    // No secret material derived on the client

499-501: Accurate auth description text

Show correct auth label for basic/bearer/api_key.

-        description: `${webhookConfig.httpMethod} ${webhookConfig.webhookUrl} • ${
-          webhookConfig.authentication === 'none' ? 'No authentication' : 'Basic authentication'
-        }`
+        description: `${webhookConfig.httpMethod} ${webhookConfig.webhookUrl} • ${
+          webhookConfig.authentication === 'none'
+            ? 'No authentication'
+            : webhookConfig.authentication === 'basic'
+              ? 'Basic authentication'
+              : webhookConfig.authentication === 'bearer'
+                ? 'Bearer token'
+                : 'API key'
+        }`

605-605: Remove unused secret array variable (update flow)

Delete credentialArray and all uses.

-    const credentialArray = await formatCredentialData()
+    // No secret material derived on the client

616-618: Accurate auth description text (update flow)

Use the same mapping for auth labels.

-        description: `${webhookConfig.httpMethod} ${webhookConfig.webhookUrl} • ${
-          webhookConfig.authentication === 'none' ? 'No authentication' : 'Basic authentication'
-        }`
+        description: `${webhookConfig.httpMethod} ${webhookConfig.webhookUrl} • ${
+          webhookConfig.authentication === 'none'
+            ? 'No authentication'
+            : webhookConfig.authentication === 'basic'
+              ? 'Basic authentication'
+              : webhookConfig.authentication === 'bearer'
+                ? 'Bearer token'
+                : 'API key'
+        }`

441-487: Critical: Remove client-side secret handling; never assemble or transmit secrets from the frontend

Persist only a credential reference (ID). Delete base64 derivation and plaintext password handling.

-    // Format credential data for backend storage
-    const formatCredentialData = async () => {
-      console.log("🔐 Formatting credential data...")
-      if (webhookConfig.authentication === "none") {
-        console.log("ℹ️ No authentication, returning empty array")
-        return []
-      }
-
-      try {
-        // Fetch all credentials of the required type
-        console.log("🔍 Fetching all credentials for auth type:", webhookConfig.authentication)
-        const allCredentials = await credentialsAPI.fetchByType(webhookConfig.authentication as "basic" | "bearer" | "api_key")
-        
-        if (allCredentials.length === 0) {
-          console.log("ℹ️ No credentials found for this auth type")
-          return []
-        }
-
-        console.log(`✅ Found ${allCredentials.length} credentials of type ${webhookConfig.authentication}`)
-
-        // Map all credentials with isSelected flag based on selectedCredential
-        const credentialData = allCredentials.map(credential => {
-          const isSelected = credential.id === webhookConfig.selectedCredential
-          
-          // Create base64 encoding of user:password for basic auth
-          const basicAuth = btoa(`${credential.user}:${credential.password}`)
-          
-          console.log(`📋 Processing credential: ${credential.name}, isSelected: ${isSelected}`)
-          
-          return {
-            user: credential.user,
-            password: credential.password, // Note: In production, this should be encrypted
-            basic_auth: basicAuth,
-            isSelected,
-            name: credential.name,
-            allowedDomains: credential.allowedDomains
-          }
-        })
-
-        console.log("📦 Formatted credential data with all credentials:", credentialData)
-        return credentialData
-      } catch (error) {
-        console.error("❌ Error formatting credential data:", error)
-        return []
-      }
-    }
+    // Persist only a reference to the selected credential; secrets are resolved server-side
+    const credentialRef = webhookConfig.selectedCredential ?? null

503-513: Do not include credentials in payload; send only selectedCredential reference

Remove credentials array; include only credentialRef.

       config: {
         // Store behavior configuration in config column
         authentication: webhookConfig.authentication,
         responseMode: webhookConfig.responseMode,
         headers: webhookConfig.headers || {},
         queryParams: webhookConfig.queryParams || {},
         options: webhookConfig.options || {},
-        credentials: credentialArray, // Array of credential objects with base64 auth
-        selectedCredential: webhookConfig.selectedCredential
+        selectedCredential: credentialRef
       }

568-605: Critical: Same fix for update flow — no client-side secret handling

Mirror the save fix: keep only the ID reference.

-    // Format credential data for backend storage
-    const formatCredentialData = async () => {
-      if (webhookConfig.authentication === "none") {
-        return []
-      }
-
-      try {
-        // Fetch all credentials of the required type
-        const allCredentials = await credentialsAPI.fetchByType(webhookConfig.authentication as "basic" | "bearer" | "api_key")
-        
-        if (allCredentials.length === 0) {
-          return []
-        }
-
-        // Map all credentials with isSelected flag based on selectedCredential
-        const credentialData = allCredentials.map(credential => {
-          const isSelected = credential.id === webhookConfig.selectedCredential
-          
-          // Create base64 encoding of user:password for basic auth
-          const basicAuth = btoa(`${credential.user}:${credential.password}`)
-          
-          return {
-            user: credential.user,
-            password: credential.password, // Note: In production, this should be encrypted
-            basic_auth: basicAuth,
-            isSelected,
-            name: credential.name,
-            allowedDomains: credential.allowedDomains
-          }
-        })
-
-        return credentialData
-      } catch (error) {
-        console.error("Error formatting credential data:", error)
-        return []
-      }
-    }
+    // Persist only a reference to the selected credential; secrets are resolved server-side
+    const credentialRef = webhookConfig.selectedCredential ?? null

621-629: Do not include credentials in update payload

Keep only the credentialRef.

         headers: webhookConfig.headers || {},
         queryParams: webhookConfig.queryParams || {},
         options: webhookConfig.options || {},
-        credentials: credentialArray, // Array of credential objects with base64 auth
-        selectedCredential: webhookConfig.selectedCredential
+        selectedCredential: credentialRef
frontend/src/components/workflow/WorkflowBuilder.tsx (1)

842-849: Guard window access for SSR-safe rendering

Avoid window in render; compute origin conditionally.

-                  const url = webhookConfig?.webhookUrl || `${window.location.origin}/webhook${webhookConfig?.path || ''}`
+                  const origin = typeof window !== "undefined" ? window.location.origin : ""
+                  const url = webhookConfig?.webhookUrl || `${origin}/webhook${webhookConfig?.path || ''}`
🧹 Nitpick comments (13)
server/services/webhookExecutionService.ts (1)

63-100: Strengthen type safety.

The helper methods use any types which reduces type safety and IntelliSense support.

Consider creating explicit types for the template and steps:

import type { InferSelectModel } from 'drizzle-orm'

type WorkflowTemplate = InferSelectModel<typeof workflowTemplate>
type WorkflowStepTemplate = InferSelectModel<typeof workflowStepTemplate>

private async getWorkflowTemplate(templateId: string): Promise<WorkflowTemplate | undefined> {
  const [template] = await db
    .select()
    .from(workflowTemplate)
    .where(eq(workflowTemplate.id, templateId))
    .limit(1)

  return template
}

private async createWorkflowExecution(template: WorkflowTemplate, context: WebhookExecutionContext) {
  // ... implementation
}

private async getWorkflowSteps(templateId: string): Promise<WorkflowStepTemplate[]> {
  // ... implementation
}
server/services/webhookAuthService.ts (1)

211-215: Use static import for crypto module.

Dynamic require for the crypto module reduces type safety and prevents tree-shaking.

Apply this diff:

+import crypto from 'crypto'
+
 export class WebhookAuthService {
   // ...

   generateWebhookSecret(): string {
     // Generate a secure random secret for webhook signature validation
-    const crypto = require('crypto')
     return crypto.randomBytes(32).toString('hex')
   }
server/services/webhookReloadService.ts (1)

45-47: Log the error object for stack/structured logging

Pass the error object to Logger.error to preserve stack/details.

-    Logger.error(`Failed to trigger webhook reload: ${errorMessage}`)
+    Logger.error(error, `Failed to trigger webhook reload: ${errorMessage}`)
server/services/webhookRegistry.ts (1)

43-44: Normalize paths consistently (leading/trailing slashes)

Prevent duplicate registrations for equivalent paths (e.g., "/foo" vs "foo/" vs "//foo").

-    const cleanPath = path.startsWith('/') ? path : `/${path}`
+    const cleanPath = `/${String(path).replace(/^\/+/, "").replace(/\/+$/, "")}`

Apply the same normalization in unregisterWebhook and getWebhook.

Also applies to: 62-63, 71-72

server/server.ts (1)

1160-1199: If you switch to the shared registry, adjust these endpoints accordingly

These use the local Map’s size/entries. With the singleton, return webhookRegistry.getAllWebhooks().length and map of getAllWebhooks().

server/api/workflow.ts (1)

2564-2573: Don’t block critical API paths on webhook reload; use fire-and-forget
In server/api/workflow.ts (lines 2565–2569 and 2609–2613), replace both await triggerWebhookReload() blocks with non-blocking calls:

-    if (hasWebhookTools(createdTools)) {
-      Logger.info("🔄 Workflow contains webhook tools, triggering webhook reload...")
-      const reloadResult = await triggerWebhookReload()
-      if (reloadResult.success) {
-        Logger.info(`✅ Webhooks reloaded successfully: ${reloadResult.count} webhooks active`)
-      } else {
-        Logger.warn(`⚠️ Webhook reload failed: ${reloadResult.error}`)
-      }
-    }
+    if (hasWebhookTools(createdTools)) {
+      Logger.info("🔄 Workflow contains webhook tools, triggering webhook reload...")
+      triggerWebhookReload()
+        .then((res) => {
+          if (res.success) Logger.info(`✅ Webhooks reloaded: ${res.count} active`)
+          else Logger.warn(`⚠️ Webhook reload failed: ${res.error}`)
+        })
+        .catch((e) => Logger.warn(`⚠️ Webhook reload threw: ${e instanceof Error ? e.message : String(e)}`))
+    }
-    Logger.info("🔄 Template updated, triggering webhook reload to ensure webhooks are current...")
-    const reloadResult = await triggerWebhookReload()
-    if (reloadResult.success) {
-      Logger.info(`✅ Webhooks reloaded successfully: ${reloadResult.count} webhooks active`)
-    } else {
-      Logger.warn(`⚠️ Webhook reload failed: ${reloadResult.error}`)
-    }
+    Logger.info("🔄 Template updated, triggering webhook reload...")
+    triggerWebhookReload()
+      .then((res) => {
+        if (res.success) Logger.info(`✅ Webhooks reloaded: ${res.count} active`)
+        else Logger.warn(`⚠️ Webhook reload failed: ${res.error}`)
+      })
+      .catch((e) => Logger.warn(`⚠️ Webhook reload threw: ${e instanceof Error ? e.message : String(e)}`))
frontend/src/components/workflow/CredentialSelector.tsx (1)

94-102: Include only relevant fields per authType in create/update
Only send user/password for "basic", token for "bearer", and apiKey for "api_key" (both in lines 94–102 and 113–123). For example:

-      const credential = await credentialsAPI.create({
-        name: newCredential.name,
-        type: authType,
-        user: newCredential.user,
-        password: newCredential.password,
-        allowedDomains: newCredential.allowedDomains
-      })
+      const base = {
+        name: newCredential.name,
+        type: authType,
+        allowedDomains: newCredential.allowedDomains
+      }
+      const payload =
+        authType === "basic"
+          ? { ...base, user: newCredential.user, password: newCredential.password }
+          : authType === "bearer"
+          ? { ...base, token: newCredential.token }
+          : authType === "api_key"
+          ? { ...base, apiKey: newCredential.apiKey }
+          : base
+      const credential = await credentialsAPI.create(payload as any)

Apply the same pattern to the update call.

frontend/src/components/CredentialModal.tsx (2)

17-22: Narrow the type of allowedDomains to a union

Prevents invalid values and aligns with Select options.

 export interface CredentialData {
   name: string
   user: string
   password: string
-  allowedDomains: string
+  allowedDomains: "All" | "Custom"
 }

169-185: Handle “Custom” domains selection

If “Custom” is chosen, provide an input to capture allowed domains; otherwise remove the option for now to avoid dead-end UX.

server/api/webhook.ts (2)

36-69: Remove or disable the custom loader to avoid duplicate/incorrect registrations

This function guesses templateId and may double-register. Prefer the integration service’s registerExistingWebhookTools.


172-179: Optionally include whitelisted headers in requestData

If downstream needs headers (e.g., signature verification), pass selected headers instead of an empty object.

-    headers: {},
+    // Consider whitelisting specific headers instead of empty object
+    headers: Object.fromEntries(new URL(c.req.url).searchParams.entries()) /* replace with a header allowlist */,
server/services/webhookIntegrationService.ts (2)

226-245: Optional: Avoid full-table scan to find templateId

Filter by toolId in SQL (array contains) to reduce load.


280-311: Optional: Validate path via DB filter instead of scanning all tools

Prefer querying for matching path in value JSON rather than iterating in JS.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b364321 and f5b2ae6.

📒 Files selected for processing (19)
  • frontend/src/components/CredentialExample.tsx (1 hunks)
  • frontend/src/components/CredentialModal.tsx (1 hunks)
  • frontend/src/components/workflow/CredentialSelector.tsx (1 hunks)
  • frontend/src/components/workflow/Types.ts (1 hunks)
  • frontend/src/components/workflow/WebhookConfigurationUI.tsx (1 hunks)
  • frontend/src/components/workflow/WorkflowBuilder.tsx (16 hunks)
  • frontend/src/components/workflow/WorkflowIcons.tsx (0 hunks)
  • frontend/src/components/workflow/api/ApiHandlers.ts (3 hunks)
  • server/api/webhook.ts (1 hunks)
  • server/api/workflow.ts (5 hunks)
  • server/manual-webhook-register.ts (1 hunks)
  • server/register-webhook.ts (1 hunks)
  • server/server.ts (2 hunks)
  • server/services/webhookAuthService.ts (1 hunks)
  • server/services/webhookExecutionService.ts (1 hunks)
  • server/services/webhookIntegrationService.ts (1 hunks)
  • server/services/webhookRegistry.ts (1 hunks)
  • server/services/webhookReloadService.ts (1 hunks)
  • server/types/workflowTypes.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • frontend/src/components/workflow/WorkflowIcons.tsx
✅ Files skipped from review due to trivial changes (1)
  • server/manual-webhook-register.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • server/types/workflowTypes.ts
  • frontend/src/components/workflow/WebhookConfigurationUI.tsx
🧰 Additional context used
🧬 Code graph analysis (13)
server/register-webhook.ts (1)
server/services/webhookRegistry.ts (1)
  • webhookRegistry (88-88)
frontend/src/components/workflow/Types.ts (2)
frontend/src/components/workflow/WebhookConfigurationUI.tsx (1)
  • WebhookConfig (548-548)
server/services/webhookRegistry.ts (1)
  • WebhookConfig (89-89)
server/services/webhookReloadService.ts (1)
server/logger/index.ts (2)
  • getLogger (36-93)
  • Subsystem (15-15)
frontend/src/components/workflow/CredentialSelector.tsx (2)
frontend/src/components/workflow/api/ApiHandlers.ts (2)
  • Credential (5-18)
  • credentialsAPI (703-841)
frontend/src/components/CredentialModal.tsx (2)
  • CredentialData (17-22)
  • CredentialModal (32-207)
server/api/workflow.ts (2)
server/services/webhookReloadService.ts (2)
  • hasWebhookTools (12-14)
  • triggerWebhookReload (20-53)
server/services/webhookRegistry.ts (1)
  • webhookRegistry (88-88)
server/api/webhook.ts (5)
server/logger/index.ts (2)
  • getLogger (36-93)
  • Subsystem (15-15)
server/db/schema/workflows.ts (1)
  • workflowTool (104-117)
frontend/src/components/workflow/Types.ts (1)
  • WebhookConfig (303-313)
server/services/webhookRegistry.ts (2)
  • WebhookConfig (89-89)
  • webhookRegistry (88-88)
server/services/webhookExecutionService.ts (1)
  • executeWorkflowFromWebhook (34-61)
server/services/webhookExecutionService.ts (2)
server/logger/index.ts (2)
  • getLogger (36-93)
  • Subsystem (15-15)
server/db/schema/workflows.ts (6)
  • workflowTemplate (60-76)
  • workflowExecution (120-140)
  • workflowStepTemplate (79-101)
  • workflowStepExecution (143-169)
  • workflowTool (104-117)
  • toolExecution (172-189)
server/services/webhookIntegrationService.ts (3)
server/logger/index.ts (2)
  • getLogger (36-93)
  • Subsystem (15-15)
server/db/schema/workflows.ts (2)
  • workflowTool (104-117)
  • workflowStepTemplate (79-101)
server/services/webhookRegistry.ts (1)
  • webhookRegistry (88-88)
server/server.ts (5)
server/services/webhookRegistry.ts (1)
  • webhookRegistry (88-88)
server/db/connector.ts (1)
  • db (3-3)
server/db/client.ts (1)
  • db (15-15)
server/db/schema/workflows.ts (5)
  • workflowTool (104-117)
  • workflowStepTemplate (79-101)
  • workflowTemplate (60-76)
  • workflowExecution (120-140)
  • workflowStepExecution (143-169)
server/services/webhookExecutionService.ts (2)
  • executeWorkflowFromWebhook (34-61)
  • startWorkflowExecution (166-185)
server/services/webhookAuthService.ts (1)
server/logger/index.ts (2)
  • getLogger (36-93)
  • Subsystem (15-15)
server/services/webhookRegistry.ts (2)
server/logger/index.ts (2)
  • getLogger (36-93)
  • Subsystem (15-15)
frontend/src/components/workflow/Types.ts (1)
  • WebhookConfig (303-313)
frontend/src/components/workflow/WorkflowBuilder.tsx (4)
frontend/src/components/workflow/WorkflowIcons.tsx (1)
  • WebhookIcon (166-184)
frontend/src/components/workflow/Types.ts (2)
  • WebhookConfig (303-313)
  • Tool (56-61)
frontend/src/components/workflow/WebhookConfigurationUI.tsx (3)
  • WebhookConfig (548-548)
  • WebhookConfigurationUI (20-545)
  • WebhookConfigurationUI (547-547)
frontend/src/components/workflow/api/ApiHandlers.ts (1)
  • workflowToolsAPI (392-635)
frontend/src/components/CredentialExample.tsx (1)
frontend/src/components/CredentialModal.tsx (2)
  • CredentialData (17-22)
  • CredentialModal (32-207)
🔇 Additional comments (5)
server/services/webhookExecutionService.ts (3)

1-15: LGTM!

Imports and logger setup follow the established patterns in the codebase.


17-32: LGTM!

The interface clearly defines webhook execution context, and the singleton pattern is correctly implemented.


34-61: LGTM!

The main orchestration flow is well-structured with proper error handling and logging.

server/services/webhookAuthService.ts (1)

1-24: LGTM!

Imports, interface definition, and singleton setup follow established patterns.

frontend/src/components/workflow/Types.ts (1)

303-313: LGTM!

The WebhookConfig interface is well-defined with appropriate types for webhook configuration. The fields align with the webhook functionality across the codebase.

Comment on lines +71 to +73
// Load webhooks on startup
loadWebhooksFromDatabase()

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: Replace ad-hoc DB loader; it registers wrong workflowTemplateId

Using tool.id as workflowTemplateId breaks execution. Delegate to the integration service that resolves template relationships and registers properly.

-// Load webhooks on startup
-loadWebhooksFromDatabase()
+// Load webhooks on startup via integration service (resolves template/tool links)
+await webhookIntegrationService.registerExistingWebhookTools()

Based on learnings

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Load webhooks on startup
loadWebhooksFromDatabase()
// Load webhooks on startup via integration service (resolves template/tool links)
await webhookIntegrationService.registerExistingWebhookTools()
🤖 Prompt for AI Agents
In server/api/webhook.ts around lines 71 to 73 the ad-hoc call
loadWebhooksFromDatabase() is using tool.id as workflowTemplateId and must be
replaced with the integration service that resolves template relationships and
performs registration correctly; remove the direct DB loader call and instead
invoke the integration service method (the service that maps tools to workflow
templates and registers webhooks), ensure you pass the correct identifiers (not
tool.id) so the service can resolve the actual workflowTemplateId, await the
service call, propagate or log errors appropriately, and add/update tests to
cover the registration flow.

Comment on lines +88 to +93
if (method !== webhookData.httpMethod) {
await webhookAuthService.auditWebhookAccess(c, path, false, `Method ${method} not allowed`)
throw new HTTPException(405, {
message: `Method ${method} not allowed. Expected ${webhookData.httpMethod}`
})
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

Method check likely should read from config; also set Allow header

If registry doesn’t expose httpMethod at top-level, read webhookData.config.httpMethod. Return an Allow header for 405.

-    if (method !== webhookData.httpMethod) {
+    const expectedMethod = webhookData.config?.httpMethod ?? webhookData.httpMethod
+    if (method !== expectedMethod) {
       await webhookAuthService.auditWebhookAccess(c, path, false, `Method ${method} not allowed`)
-      throw new HTTPException(405, { 
-        message: `Method ${method} not allowed. Expected ${webhookData.httpMethod}` 
-      })
+      c.header("Allow", expectedMethod)
+      throw new HTTPException(405, { 
+        message: `Method ${method} not allowed. Expected ${expectedMethod}` 
+      })
     }

Run to confirm registry shapes:


🏁 Script executed:

#!/bin/bash
# Inspect registry API to verify where httpMethod lives and getWebhook shape
fd -a webhookRegistry.ts
rg -n -C3 'registerWebhook\s*\('
rg -n -C3 'getWebhook\s*\('
rg -n -C3 'interface\s+.*Webhook' server/services/webhookRegistry.ts || true

Length of output: 8759


🏁 Script executed:

#!/bin/bash
# Display WebhookRegistrationService implementation and getWebhook return type
echo "=== server/services/webhookRegistry.ts (lines 1-200) ==="
sed -n '1,200p' server/services/webhookRegistry.ts

echo -e "\n=== server/api/webhook.ts (lines 70-120) ==="
sed -n '70,120p' server/api/webhook.ts

Length of output: 4510


Add Allow header on 405 response
In server/api/webhook.ts’s handleWebhookRequest, before throwing the 405, set the Allow header using the existing webhookData.httpMethod (which already reflects the configured method).
Example:

await webhookAuthService.auditWebhookAccess(c, path, false, `Method ${method} not allowed`)
c.header("Allow", webhookData.httpMethod)
throw new HTTPException(405, {
  message: `Method ${method} not allowed. Expected ${webhookData.httpMethod}`
})
🤖 Prompt for AI Agents
In server/api/webhook.ts around lines 88 to 93, before throwing the 405
HTTPException when the request method doesn't match webhookData.httpMethod, set
the response "Allow" header to webhookData.httpMethod and then throw the 405;
keep the existing auditWebhookAccess call, call c.header("Allow",
webhookData.httpMethod) immediately after auditing and before throwing, and
ensure the header value matches the configured method string.

Comment on lines +2777 to +2812
// If this is a webhook tool, register the webhook
if (tool.type === ToolType.WEBHOOK && tool.config && tool.value) {
try {
const config = tool.config as any
const value = tool.value as any

if (config.path || value.path) {
const webhookConfig = {
webhookUrl: value.webhookUrl || `http://localhost:3000/webhook${config.path || value.path}`,
httpMethod: config.httpMethod || 'POST',
path: config.path || value.path,
authentication: config.authentication || 'none',
selectedCredential: config.selectedCredential,
responseMode: config.responseMode || 'immediately',
options: config.options || {},
headers: config.headers || {},
queryParams: config.queryParams || {}
}

// Get workflow template ID (simplified - in real implementation you'd get this from context)
const templateId = requestData.workflowTemplateId || 'default-template'

await webhookRegistry.registerWebhook(
webhookConfig.path,
templateId,
tool.id,
webhookConfig
)

Logger.info(`Registered webhook ${webhookConfig.path} for tool ${tool.id}`)
}
} catch (webhookError) {
Logger.error(webhookError, `Failed to register webhook for tool ${tool.id}`)
// Don't fail the tool creation if webhook registration fails
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Wrong registry and unsafe default template; unify registration or just reload

This registers into services/webhookRegistry, but runtime uses a different in-memory Map in server/server.ts. Registration here won’t affect live webhook handling. Also, using 'default-template' when workflowTemplateId isn’t provided can misroute executions.

Option A (simplest): drop per-tool registration and trigger a reload.

-    // If this is a webhook tool, register the webhook
-    if (tool.type === ToolType.WEBHOOK && tool.config && tool.value) {
-      try {
-        const config = tool.config as any
-        const value = tool.value as any
-        
-        if (config.path || value.path) {
-          const webhookConfig = {
-            webhookUrl: value.webhookUrl || `http://localhost:3000/webhook${config.path || value.path}`,
-            httpMethod: config.httpMethod || 'POST',
-            path: config.path || value.path,
-            authentication: config.authentication || 'none',
-            selectedCredential: config.selectedCredential,
-            responseMode: config.responseMode || 'immediately',
-            options: config.options || {},
-            headers: config.headers || {},
-            queryParams: config.queryParams || {}
-          }
-          
-          // Get workflow template ID (simplified - in real implementation you'd get this from context)
-          const templateId = requestData.workflowTemplateId || 'default-template'
-          
-          await webhookRegistry.registerWebhook(
-            webhookConfig.path,
-            templateId,
-            tool.id,
-            webhookConfig
-          )
-          
-          Logger.info(`Registered webhook ${webhookConfig.path} for tool ${tool.id}`)
-        }
-      } catch (webhookError) {
-        Logger.error(webhookError, `Failed to register webhook for tool ${tool.id}`)
-        // Don't fail the tool creation if webhook registration fails
-      }
-    }
+    // If this is a webhook tool, trigger a registry reload (runtime source of truth)
+    if (tool.type === ToolType.WEBHOOK) {
+      triggerWebhookReload().catch((e) =>
+        Logger.warn(`Webhook reload after tool create failed: ${e instanceof Error ? e.message : String(e)}`),
+      )
+    }

Option B: migrate server/server.ts to use the shared services/webhookRegistry for a single source of truth, then keep per-tool register/unregister. I can draft that refactor.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// If this is a webhook tool, register the webhook
if (tool.type === ToolType.WEBHOOK && tool.config && tool.value) {
try {
const config = tool.config as any
const value = tool.value as any
if (config.path || value.path) {
const webhookConfig = {
webhookUrl: value.webhookUrl || `http://localhost:3000/webhook${config.path || value.path}`,
httpMethod: config.httpMethod || 'POST',
path: config.path || value.path,
authentication: config.authentication || 'none',
selectedCredential: config.selectedCredential,
responseMode: config.responseMode || 'immediately',
options: config.options || {},
headers: config.headers || {},
queryParams: config.queryParams || {}
}
// Get workflow template ID (simplified - in real implementation you'd get this from context)
const templateId = requestData.workflowTemplateId || 'default-template'
await webhookRegistry.registerWebhook(
webhookConfig.path,
templateId,
tool.id,
webhookConfig
)
Logger.info(`Registered webhook ${webhookConfig.path} for tool ${tool.id}`)
}
} catch (webhookError) {
Logger.error(webhookError, `Failed to register webhook for tool ${tool.id}`)
// Don't fail the tool creation if webhook registration fails
}
}
// If this is a webhook tool, trigger a registry reload (runtime source of truth)
if (tool.type === ToolType.WEBHOOK) {
triggerWebhookReload().catch((e) =>
Logger.warn(
`Webhook reload after tool create failed: ${
e instanceof Error ? e.message : String(e)
}`,
),
)
}
🤖 Prompt for AI Agents
In server/api/workflow.ts around lines 2777-2812, the code registers webhooks
into services/webhookRegistry (which is not the runtime registry) and falls back
to the unsafe 'default-template' id; instead, remove the per-tool call to
webhookRegistry.register and trigger the runtime to reload its live webhook map
(or emit the existing reload/update event) so the live in-memory registry in
server/server.ts picks up the new tool; also stop using 'default-template' —
require requestData.workflowTemplateId (return a 4xx if missing) or pass the
explicit template id from context so webhooks are mapped correctly. Ensure the
try/catch still prevents tool creation failure but uses the reload mechanism or
error response rather than registering in the wrong registry.

Comment on lines +2894 to +2939
// If this is a webhook tool, update the webhook registration
if (result.tool.type === ToolType.WEBHOOK && result.tool.config && result.tool.value) {
try {
const config = result.tool.config as any
const value = result.tool.value as any

if (config.path || value.path) {
const webhookConfig = {
webhookUrl: value.webhookUrl || `http://localhost:3000/webhook${config.path || value.path}`,
httpMethod: config.httpMethod || 'POST',
path: config.path || value.path,
authentication: config.authentication || 'none',
selectedCredential: config.selectedCredential,
responseMode: config.responseMode || 'immediately',
options: config.options || {},
headers: config.headers || {},
queryParams: config.queryParams || {}
}

// Get workflow template ID (simplified - in real implementation you'd get this from context)
const templateId = requestData.workflowTemplateId || 'default-template'

// Unregister old webhook first (if path changed)
if (existingTool[0] && existingTool[0].value) {
const oldValue = existingTool[0].value as any
if (oldValue.path && oldValue.path !== webhookConfig.path) {
await webhookRegistry.unregisterWebhook(oldValue.path)
}
}

// Register new/updated webhook
await webhookRegistry.registerWebhook(
webhookConfig.path,
templateId,
result.tool.id,
webhookConfig
)

Logger.info(`Updated webhook registration ${webhookConfig.path} for tool ${result.tool.id}`)
}
} catch (webhookError) {
Logger.error(webhookError, `Failed to update webhook for tool ${result.tool.id}`)
// Don't fail the tool update if webhook registration fails
}
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Unregister old path robustly and avoid hardcoded base URL

  • Only checking oldValue.path misses old config.path. Unregister whichever old path exists.
  • Avoid http://localhost:3000; derive from config.port.
-        const config = result.tool.config as any
-        const value = result.tool.value as any
+        const config = result.tool.config as any
+        const value = result.tool.value as any
         
         if (config.path || value.path) {
-          const webhookConfig = {
-            webhookUrl: value.webhookUrl || `http://localhost:3000/webhook${config.path || value.path}`,
+          const webhookConfig = {
+            webhookUrl: value.webhookUrl || `http://127.0.0.1:${config.port}/webhook${config.path || value.path}`,
             httpMethod: config.httpMethod || 'POST',
             path: config.path || value.path,
             authentication: config.authentication || 'none',
             selectedCredential: config.selectedCredential,
             responseMode: config.responseMode || 'immediately',
             options: config.options || {},
             headers: config.headers || {},
             queryParams: config.queryParams || {}
           }
           
           // Get workflow template ID (simplified - in real implementation you'd get this from context)
           const templateId = requestData.workflowTemplateId || 'default-template'
           
           // Unregister old webhook first (if path changed)
-          if (existingTool[0] && existingTool[0].value) {
-            const oldValue = existingTool[0].value as any
-            if (oldValue.path && oldValue.path !== webhookConfig.path) {
-              await webhookRegistry.unregisterWebhook(oldValue.path)
-            }
-          }
+          const prevCfg = existingTool[0]?.config as any
+          const prevVal = existingTool[0]?.value as any
+          const oldPath = (prevCfg && prevCfg.path) || (prevVal && prevVal.path)
+          if (oldPath && oldPath !== webhookConfig.path) {
+            await webhookRegistry.unregisterWebhook(oldPath)
+          }

Note: consider eliminating the 'default-template' fallback; require a real templateId or skip registration.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// If this is a webhook tool, update the webhook registration
if (result.tool.type === ToolType.WEBHOOK && result.tool.config && result.tool.value) {
try {
const config = result.tool.config as any
const value = result.tool.value as any
if (config.path || value.path) {
const webhookConfig = {
webhookUrl: value.webhookUrl || `http://localhost:3000/webhook${config.path || value.path}`,
httpMethod: config.httpMethod || 'POST',
path: config.path || value.path,
authentication: config.authentication || 'none',
selectedCredential: config.selectedCredential,
responseMode: config.responseMode || 'immediately',
options: config.options || {},
headers: config.headers || {},
queryParams: config.queryParams || {}
}
// Get workflow template ID (simplified - in real implementation you'd get this from context)
const templateId = requestData.workflowTemplateId || 'default-template'
// Unregister old webhook first (if path changed)
if (existingTool[0] && existingTool[0].value) {
const oldValue = existingTool[0].value as any
if (oldValue.path && oldValue.path !== webhookConfig.path) {
await webhookRegistry.unregisterWebhook(oldValue.path)
}
}
// Register new/updated webhook
await webhookRegistry.registerWebhook(
webhookConfig.path,
templateId,
result.tool.id,
webhookConfig
)
Logger.info(`Updated webhook registration ${webhookConfig.path} for tool ${result.tool.id}`)
}
} catch (webhookError) {
Logger.error(webhookError, `Failed to update webhook for tool ${result.tool.id}`)
// Don't fail the tool update if webhook registration fails
}
}
// If this is a webhook tool, update the webhook registration
if (result.tool.type === ToolType.WEBHOOK && result.tool.config && result.tool.value) {
try {
const config = result.tool.config as any
const value = result.tool.value as any
if (config.path || value.path) {
const webhookConfig = {
webhookUrl: value.webhookUrl || `http://127.0.0.1:${config.port}/webhook${config.path || value.path}`,
httpMethod: config.httpMethod || 'POST',
path: config.path || value.path,
authentication: config.authentication || 'none',
selectedCredential: config.selectedCredential,
responseMode: config.responseMode || 'immediately',
options: config.options || {},
headers: config.headers || {},
queryParams: config.queryParams || {}
}
// Get workflow template ID (simplified - in real implementation you'd get this from context)
const templateId = requestData.workflowTemplateId || 'default-template'
// Unregister old webhook first (if path changed)
const prevCfg = existingTool[0]?.config as any
const prevVal = existingTool[0]?.value as any
const oldPath = (prevCfg && prevCfg.path) || (prevVal && prevVal.path)
if (oldPath && oldPath !== webhookConfig.path) {
await webhookRegistry.unregisterWebhook(oldPath)
}
// Register new/updated webhook
await webhookRegistry.registerWebhook(
webhookConfig.path,
templateId,
result.tool.id,
webhookConfig
)
Logger.info(`Updated webhook registration ${webhookConfig.path} for tool ${result.tool.id}`)
}
} catch (webhookError) {
Logger.error(webhookError, `Failed to update webhook for tool ${result.tool.id}`)
// Don't fail the tool update if webhook registration fails
}
}
🤖 Prompt for AI Agents
In server/api/workflow.ts around lines 2894 to 2939, the webhook-update block
should unregister the previous path from either the old value or old config (not
just oldValue.path), avoid hardcoding the base URL, and stop using a fallback
'default-template'; instead require a real workflowTemplateId or skip
registration. Fix by checking both existingTool[0].value.path and
existingTool[0].config.path when deciding what to unregister, build webhookUrl
from the runtime config (e.g., derive host/port from config or request
environment) instead of using "http://localhost:3000", and if
requestData.workflowTemplateId is missing skip registration and log a warning
(do not use 'default-template').

Comment on lines +4 to +36
async function registerTestWebhook() {
try {
console.log('🔧 Manually registering webhook...')

const webhookConfig = {
webhookUrl: "http://localhost:3000/webhook/test",
httpMethod: "POST" as const,
path: "/test",
authentication: "none" as const,
responseMode: "immediately" as const,
headers: {},
queryParams: {},
options: {}
}

await webhookRegistry.registerWebhook(
"/test",
"manual-template-id",
"manual-tool-id",
webhookConfig
)

console.log('✅ Webhook registered successfully!')
console.log('🔗 Test with: curl -X POST "http://localhost:3000/webhook/test" -H "Content-Type: application/json" -d \'{"test": true}\'')

// List all webhooks
const webhooks = webhookRegistry.getAllWebhooks()
console.log('📋 All registered webhooks:', webhooks)

} catch (error) {
console.error('❌ Registration failed:', error)
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Use valid database IDs or document requirements.

Lines 21-22 use hardcoded placeholder IDs ("manual-template-id" and "manual-tool-id") that don't exist in the database. This will cause runtime failures when the webhook is triggered and the workflow execution service tries to query these entities.

Consider one of these approaches:

  1. Query for valid IDs dynamically:
async function registerTestWebhook() {
  try {
    console.log('🔧 Manually registering webhook...')
    
    // Fetch a real workflow template
    const [template] = await db
      .select()
      .from(workflowTemplate)
      .limit(1)
    
    if (!template) {
      throw new Error('No workflow templates found. Create one first.')
    }
    
    // Fetch or create a webhook tool
    const [tool] = await db
      .select()
      .from(workflowTool)
      .where(eq(workflowTool.type, ToolType.WEBHOOK))
      .limit(1)
    
    if (!tool) {
      throw new Error('No webhook tools found. Create one first.')
    }
    
    const webhookConfig = {
      webhookUrl: "http://localhost:3000/webhook/test",
      httpMethod: "POST" as const,
      path: "/test",
      authentication: "none" as const,
      responseMode: "immediately" as const,
      headers: {},
      queryParams: {},
      options: {}
    }

    await webhookRegistry.registerWebhook(
      "/test",
      template.id,
      tool.id,
      webhookConfig
    )

    console.log('✅ Webhook registered successfully!')
    console.log('🔗 Test with: curl -X POST "http://localhost:3000/webhook/test" -H "Content-Type: application/json" -d \'{"test": true}\'')
    
    const webhooks = webhookRegistry.getAllWebhooks()
    console.log('📋 All registered webhooks:', webhooks)
    
  } catch (error) {
    console.error('❌ Registration failed:', error)
  }
}
  1. Or add clear documentation:
// NOTE: Replace these placeholder IDs with actual template and tool IDs from your database:
// 1. Query workflow_template table for a valid template ID
// 2. Query workflow_tool table for a valid tool ID with type='webhook'
await webhookRegistry.registerWebhook(
  "/test",
  "REPLACE_WITH_REAL_TEMPLATE_ID", // e.g., from workflow_template.id
  "REPLACE_WITH_REAL_TOOL_ID",     // e.g., from workflow_tool.id where type='webhook'
  webhookConfig
)
🤖 Prompt for AI Agents
In server/register-webhook.ts around lines 4 to 36: the code registers a test
webhook using hardcoded placeholder IDs ("manual-template-id" and
"manual-tool-id") which will fail at runtime because those entities don't exist;
replace the placeholders with valid IDs by querying the database for a real
workflow template and a webhook-type workflow tool (or throw a clear error if
none exist), then pass template.id and tool.id into
webhookRegistry.registerWebhook, or alternatively add a prominent comment
instructing developers to replace the placeholders with actual IDs from the
workflow_template and workflow_tool tables before running.

Comment on lines +217 to +235
async completeExecution(executionId: string, result?: any) {
try {
await db
.update(workflowExecution)
.set({
status: WorkflowStatus.COMPLETED,
completedAt: new Date(),
metadata: result ? { ...result, completedAt: new Date().toISOString() } : undefined,
updatedAt: new Date()
})
.where(eq(workflowExecution.id, executionId))

Logger.info(`Workflow execution ${executionId} completed`)

} catch (error) {
Logger.error(`Failed to complete execution ${executionId}: ${error}`)
throw error
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Preserve existing metadata when completing execution.

Line 224 completely overwrites the execution's metadata when result is provided, which will lose important trigger information (webhookPath, requestData, triggeredAt) that was stored during creation.

Apply this diff to merge metadata instead:

   async completeExecution(executionId: string, result?: any) {
     try {
+      // Fetch current execution to preserve existing metadata
+      const [currentExecution] = await db
+        .select()
+        .from(workflowExecution)
+        .where(eq(workflowExecution.id, executionId))
+        .limit(1)
+
       await db
         .update(workflowExecution)
         .set({
           status: WorkflowStatus.COMPLETED,
           completedAt: new Date(),
-          metadata: result ? { ...result, completedAt: new Date().toISOString() } : undefined,
+          metadata: result 
+            ? { ...(currentExecution?.metadata || {}), ...result, completedAt: new Date().toISOString() } 
+            : { ...(currentExecution?.metadata || {}), completedAt: new Date().toISOString() },
           updatedAt: new Date()
         })
         .where(eq(workflowExecution.id, executionId))

       Logger.info(`Workflow execution ${executionId} completed`)

     } catch (error) {
       Logger.error(`Failed to complete execution ${executionId}: ${error}`)
       throw error
     }
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async completeExecution(executionId: string, result?: any) {
try {
await db
.update(workflowExecution)
.set({
status: WorkflowStatus.COMPLETED,
completedAt: new Date(),
metadata: result ? { ...result, completedAt: new Date().toISOString() } : undefined,
updatedAt: new Date()
})
.where(eq(workflowExecution.id, executionId))
Logger.info(`Workflow execution ${executionId} completed`)
} catch (error) {
Logger.error(`Failed to complete execution ${executionId}: ${error}`)
throw error
}
}
async completeExecution(executionId: string, result?: any) {
try {
// Fetch current execution to preserve existing metadata
const [currentExecution] = await db
.select()
.from(workflowExecution)
.where(eq(workflowExecution.id, executionId))
.limit(1)
await db
.update(workflowExecution)
.set({
status: WorkflowStatus.COMPLETED,
completedAt: new Date(),
metadata: result
? { ...(currentExecution?.metadata || {}), ...result, completedAt: new Date().toISOString() }
: { ...(currentExecution?.metadata || {}), completedAt: new Date().toISOString() },
updatedAt: new Date()
})
.where(eq(workflowExecution.id, executionId))
Logger.info(`Workflow execution ${executionId} completed`)
} catch (error) {
Logger.error(`Failed to complete execution ${executionId}: ${error}`)
throw error
}
}

Comment on lines +237 to +257
async failExecution(executionId: string, error: any) {
try {
await db
.update(workflowExecution)
.set({
status: WorkflowStatus.FAILED,
metadata: {
error: error.toString(),
failedAt: new Date().toISOString()
},
updatedAt: new Date()
})
.where(eq(workflowExecution.id, executionId))

Logger.error(`Workflow execution ${executionId} failed: ${error}`)

} catch (dbError) {
Logger.error(`Failed to mark execution ${executionId} as failed: ${dbError}`)
throw dbError
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Preserve existing metadata when failing execution.

Similar to completeExecution, this method overwrites all metadata instead of merging with existing data.

Apply this diff:

   async failExecution(executionId: string, error: any) {
     try {
+      // Fetch current execution to preserve existing metadata
+      const [currentExecution] = await db
+        .select()
+        .from(workflowExecution)
+        .where(eq(workflowExecution.id, executionId))
+        .limit(1)
+
       await db
         .update(workflowExecution)
         .set({
           status: WorkflowStatus.FAILED,
-          metadata: { 
+          metadata: {
+            ...(currentExecution?.metadata || {}),
             error: error.toString(), 
             failedAt: new Date().toISOString() 
           },
           updatedAt: new Date()
         })
         .where(eq(workflowExecution.id, executionId))

       Logger.error(`Workflow execution ${executionId} failed: ${error}`)

     } catch (dbError) {
       Logger.error(`Failed to mark execution ${executionId} as failed: ${dbError}`)
       throw dbError
     }
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async failExecution(executionId: string, error: any) {
try {
await db
.update(workflowExecution)
.set({
status: WorkflowStatus.FAILED,
metadata: {
error: error.toString(),
failedAt: new Date().toISOString()
},
updatedAt: new Date()
})
.where(eq(workflowExecution.id, executionId))
Logger.error(`Workflow execution ${executionId} failed: ${error}`)
} catch (dbError) {
Logger.error(`Failed to mark execution ${executionId} as failed: ${dbError}`)
throw dbError
}
}
async failExecution(executionId: string, error: any) {
try {
// Fetch current execution to preserve existing metadata
const [currentExecution] = await db
.select()
.from(workflowExecution)
.where(eq(workflowExecution.id, executionId))
.limit(1)
await db
.update(workflowExecution)
.set({
status: WorkflowStatus.FAILED,
metadata: {
...(currentExecution?.metadata || {}),
error: error.toString(),
failedAt: new Date().toISOString(),
},
updatedAt: new Date()
})
.where(eq(workflowExecution.id, executionId))
Logger.error(`Workflow execution ${executionId} failed: ${error}`)
} catch (dbError) {
Logger.error(`Failed to mark execution ${executionId} as failed: ${dbError}`)
throw dbError
}
}

Comment on lines +99 to +103
// Unregister old webhook if path is changing
if (config.path && config.path !== existingValue?.path) {
await webhookRegistry.unregisterWebhook(existingValue.path)
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Guard unregister when old path is undefined

Prevent calling unregister with undefined.

-      if (config.path && config.path !== existingValue?.path) {
-        await webhookRegistry.unregisterWebhook(existingValue.path)
-      }
+      if (config.path && config.path !== existingValue?.path) {
+        if (existingValue?.path) {
+          await webhookRegistry.unregisterWebhook(existingValue.path)
+        }
+      }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Unregister old webhook if path is changing
if (config.path && config.path !== existingValue?.path) {
await webhookRegistry.unregisterWebhook(existingValue.path)
}
// Unregister old webhook if path is changing
if (config.path && config.path !== existingValue?.path) {
if (existingValue?.path) {
await webhookRegistry.unregisterWebhook(existingValue.path)
}
}
🤖 Prompt for AI Agents
In server/services/webhookIntegrationService.ts around lines 99 to 103, the code
calls webhookRegistry.unregisterWebhook(existingValue.path) without ensuring
existingValue.path is defined; add a guard so you only call unregisterWebhook
when existingValue?.path is truthy (e.g., change condition to check config.path
&& config.path !== existingValue?.path && existingValue?.path) to prevent
passing undefined to unregisterWebhook.

Comment on lines +131 to +146
// Re-register webhook with new configuration
if (config.path) {
const fullConfig = { ...existingConfig, ...updatedConfig, path: config.path }
// Get workflow template ID from step template
const templateId = await this.getWorkflowTemplateIdFromTool(toolId)

if (templateId) {
await webhookRegistry.registerWebhook(
config.path,
templateId,
toolId,
fullConfig as WebhookToolConfig
)
}
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Update registry even when only config changes (path unchanged)

Currently, changed httpMethod/auth/headers aren’t reflected in the registry unless path changes.

-      // Re-register webhook with new configuration
-      if (config.path) {
-        const fullConfig = { ...existingConfig, ...updatedConfig, path: config.path }
-        // Get workflow template ID from step template
-        const templateId = await this.getWorkflowTemplateIdFromTool(toolId)
-        
-        if (templateId) {
-          await webhookRegistry.registerWebhook(
-            config.path,
-            templateId,
-            toolId,
-            fullConfig as WebhookToolConfig
-          )
-        }
-      }
+      // Re-register (or refresh) webhook with updated configuration
+      const pathToUse: string | undefined = (config.path ?? existingValue?.path)
+      if (pathToUse) {
+        const fullConfig = { ...existingConfig, ...updatedConfig, path: pathToUse }
+        // Get workflow template ID from step template
+        const templateId = await this.getWorkflowTemplateIdFromTool(toolId)
+        if (templateId) {
+          await webhookRegistry.registerWebhook(
+            pathToUse,
+            templateId,
+            toolId,
+            fullConfig as WebhookToolConfig
+          )
+        }
+      }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In server/services/webhookIntegrationService.ts around lines 131 to 146, the
code only calls webhookRegistry.registerWebhook when config.path is present, so
changes to httpMethod/auth/headers are not propagated if the path is unchanged;
update the logic to always compute fullConfig (merge existingConfig and
updatedConfig), determine the effective path (use config.path if present else
existingConfig.path), fetch the workflow template ID as before, and call
webhookRegistry.registerWebhook with the effective path, templateId, toolId and
fullConfig so registry entries are updated even when only non-path config fields
change.

Comment on lines +25 to +34
const response = await fetch('http://localhost:3000/webhook-api/reload', {
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
})

if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Avoid hardcoded localhost and add request timeout (prevents hung request on API path)

This GET uses a fixed port and no timeout. On request threads, a hung call can stall the API. Use config.port and AbortController with a short timeout.

Apply:

-    // Make internal HTTP request to the webhook reload endpoint
-    const response = await fetch('http://localhost:3000/webhook-api/reload', {
-      method: 'GET',
-      headers: {
-        'Content-Type': 'application/json'
-      }
-    })
+    // Make internal HTTP request to the webhook reload endpoint (with timeout)
+    const controller = new AbortController()
+    const timeoutId = setTimeout(() => controller.abort(), 5000)
+    const baseUrl = `http://127.0.0.1:${config.port}`
+    const response = await fetch(`${baseUrl}/webhook-api/reload`, {
+      method: 'GET',
+      signal: controller.signal,
+    })
+    clearTimeout(timeoutId)

And add at top of file:

import config from "@/config"
🤖 Prompt for AI Agents
In server/services/webhookReloadService.ts around lines 25 to 34, the fetch call
is using a hardcoded localhost:3000 URL and has no timeout; change it to use
config.port (import config from "@/config" at top of the file) and wrap the
fetch with an AbortController that times out (e.g., 2–5s) by calling setTimeout
to abort the controller and clearing the timer after response; build the URL
using `http://localhost:${config.port}/webhook-api/reload`, pass signal to
fetch, and handle AbortError to throw a clear timeout error and ensure the timer
is cleaned up in all code paths.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants