feat/389-Webhooks-Headers-Query-params-Methods-validation#6217
Conversation
…n both canvas, agentflow is out of scope but shows temporary ui
useParams() does not update when window.history.replaceState() is used on first save (bypasses React Router). Fall back to Redux canvas.chatflow.id so NodeInputHandler re-renders reactively when SET_CHATFLOW is dispatched.
There was a problem hiding this comment.
Code Review
This pull request introduces a webhookTrigger start type for Agent Flows, enabling workflows to be triggered via external HTTP requests with configurable validation for methods, headers, and parameters. It also adds support for $webhook.* variables and provides a UI for webhook URL management. Review feedback identifies the need for case-insensitive header handling in both validation and variable resolution to align with Express, and recommends passing the webhook payload as an object to maintain property accessibility in downstream nodes.
| // Required header validation | ||
| const rawHeaderParams = startNode?.data?.inputs?.webhookHeaderParams | ||
| const webhookHeaderParams: Array<{ name: string; required: boolean }> = Array.isArray(rawHeaderParams) ? rawHeaderParams : [] | ||
| const missingHeaders = webhookHeaderParams.filter((p) => p.required && headers?.[p.name] == null).map((p) => p.name) |
There was a problem hiding this comment.
HTTP header names are case-insensitive. Express automatically lowercases all header keys in req.headers. If a user configures a required header with mixed casing (e.g., X-API-Key), this check will fail because it looks for the exact casing in the headers object. You should lowercase the configured header name before checking its presence.
| const missingHeaders = webhookHeaderParams.filter((p) => p.required && headers?.[p.name] == null).map((p) => p.name) | |
| const missingHeaders = webhookHeaderParams.filter((p) => p.required && headers?.[p.name.toLowerCase()] == null).map((p) => p.name) |
| .map(([key, value]) => `${key}: ${value}`) | ||
| .join('\n') | ||
| } else if (incomingInput.webhook) { | ||
| finalInput = JSON.stringify(incomingInput.webhook) |
There was a problem hiding this comment.
Stringifying the webhook payload here causes the Start node to receive a string instead of an object. This prevents subsequent nodes from accessing nested properties via the start node's output (e.g., {{startAgentflow_0.output.webhook.body.foo}}). Since the run method of nodes can handle objects, it's better to pass the webhook object directly.
finalInput = incomingInput.webhookThere was a problem hiding this comment.
nice catch but the fix was in a different location.
Doing as gemini told to pass webhook object directly broke downstream nodes because it was expected to be string
Instead, the fix belongs in the Start node's run(), parse the JSON string back into an object before setting outputData.webhook, so nested path access like {{startAgentflow_0.output.webhook.body.action}} works without affecting the rest of the pipeline.
…k trigger - Add webhookTrigger input type to Start node with HTTP method, content type, and expected headers/query/body param configuration - New /api/v1/webhook/:id route with method, content-type, header, body, and query param validation (400/405/415 on mismatch) - Namespace webhook payload as $webhook.body.*, $webhook.headers.*, $webhook.query.* in the flow runtime - Resolve $webhook.* variables in downstream nodes via buildAgentflow.ts - Auto-unwrap form-encoded `payload` JSON strings (e.g. GitHub webhooks) so $webhook.body.* paths work regardless of content type - Expose webhook variable suggestions in the node variable picker - Show copyable webhook URL in the Start node canvas UI
c43b746 to
58f4676
Compare
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request enhances the Agentflow Webhook trigger by adding configuration for HTTP methods, Content-Type, and validation for headers, query parameters, and body parameters. It also introduces automatic JSON parsing for form-encoded payloads and namespaces webhook data for better accessibility. Feedback highlights the need for case-insensitive Content-Type validation and more flexible type checking for form-encoded data.
|
|
||
| // Content-Type validation (startsWith handles "application/json; charset=utf-8" variants) | ||
| const webhookContentType = startNode?.data?.inputs?.webhookContentType | ||
| if (webhookContentType && !headers?.['content-type']?.startsWith(webhookContentType)) { |
There was a problem hiding this comment.
The Content-Type validation is case-sensitive, which can lead to unexpected failures if a client sends the header with different casing (e.g., Application/JSON). Since HTTP headers are case-insensitive, the incoming header value should be normalized to lowercase before comparison.
const incomingContentType = (headers?.['content-type'] ?? '').toLowerCase()
if (webhookContentType && !incomingContentType.startsWith(webhookContentType.toLowerCase())) {There was a problem hiding this comment.
webhookContentType will be lowercase since its hardcoded in the dropdown
|
|
||
| // Body type validation (only for params that have an explicit type declared) | ||
| const typeMismatch = webhookBodyParams | ||
| .filter((p) => p.type != null && body?.[p.name] != null && typeof body[p.name] !== p.type) |
There was a problem hiding this comment.
The type validation using typeof may be too restrictive for application/x-www-form-urlencoded requests where numeric or boolean values are transmitted as strings. If the webhook is intended to support standard form-encoded data (outside of the payload JSON wrapper), consider allowing string-to-number or string-to-boolean conversions or loosening this check.
There was a problem hiding this comment.
this is fine since controller already unwraps JSON payload field before validation runs, preserving proper types.
This is done in src/controllers/webhook/index.ts line 22
… and a lowercase headers issue
70ef829 to
847d803
Compare
…-Query-Params-Methods-Validation-Etc
| } | ||
| ] | ||
| }, | ||
| { |
There was a problem hiding this comment.
might need to add client type to avoid showing this in internal app
* feat(ambient-agents): Add webhook trigger UI on start node (#6068) * feat(ambient-agents): Add webhook trigger UI on start node, handles in both canvas, agentflow is out of scope but shows temporary ui * fix: resolve webhookURL copy button not appearing after first save useParams() does not update when window.history.replaceState() is used on first save (bypasses React Router). Fall back to Redux canvas.chatflow.id so NodeInputHandler re-renders reactively when SET_CHATFLOW is dispatched. * feat/365-366-Webhooks-Server-Route-And-Execution (#6164) * feat(webhooks): add server route and validation for webhook trigger - POST /api/v1/webhook/:id route (accepts all HTTP methods via router.all) - Validates chatflow exists and is configured as webhookTrigger, returns 404 otherwise - Wraps raw webhook payload as incomingInput.webhook for buildAgentflow Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(agentflow): wire up webhook flow execution (FLOWISE-366) - Resolve {{ $webhook.field }} template variables in agentflow nodes - Add required body param validation in webhook service - Whitelist /api/v1/webhook/ to bypass global auth middleware - Set $input to JSON payload in custom function nodes for webhook flows - Add $webhook. autocomplete suggestions in node editors - Unit tests for body param validation and pre-mutation body pass-through Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(webhooks): namespace webhook payload under $webhook.body.* - Wrap webhook body as { webhook: { body } } in controller so $webhook.body.*, $webhook.headers.*, and $webhook.query.* can coexist as distinct namespaces - Update suggestion option IDs/labels in UI from $webhook.* to $webhook.body.* - Restrict webhookTrigger start option to agentflowv2 client only - Remove static webhookURL placeholder from NodeInputHandler (agentflow) --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * feat/389-Webhooks-Headers-Query-params-Methods-validation (#6217) * feat(ambient-agents): Add webhook trigger UI on start node, handles in both canvas, agentflow is out of scope but shows temporary ui * fix: resolve webhookURL copy button not appearing after first save useParams() does not update when window.history.replaceState() is used on first save (bypasses React Router). Fall back to Redux canvas.chatflow.id so NodeInputHandler re-renders reactively when SET_CHATFLOW is dispatched. * feat(webhooks): add headers, query params & body validation to webhook trigger - Add webhookTrigger input type to Start node with HTTP method, content type, and expected headers/query/body param configuration - New /api/v1/webhook/:id route with method, content-type, header, body, and query param validation (400/405/415 on mismatch) - Namespace webhook payload as $webhook.body.*, $webhook.headers.*, $webhook.query.* in the flow runtime - Resolve $webhook.* variables in downstream nodes via buildAgentflow.ts - Auto-unwrap form-encoded `payload` JSON strings (e.g. GitHub webhooks) so $webhook.body.* paths work regardless of content type - Expose webhook variable suggestions in the node variable picker - Show copyable webhook URL in the Start node canvas UI * fixed a bug where downstream nodes cant reference values via node id, and a lowercase headers issue * feat:533 webhook secrets (#6227) * feat: add webhook secret & HMAC signature verification to webhook trigger Adds server-side webhook secret management (generate/clear/verify) and a UI control in the Start node for configuring the secret, signature header, and signature type (HMAC-SHA256 or plain token). Raw request body is now captured before JSON parsing so HMAC signatures can be verified against the original bytes. Migrations added for all four supported databases. * fix: accept string-coerced numbers and booleans in webhook body type validation application/x-www-form-urlencoded payloads deliver all values as strings, so the strict typeof check was incorrectly rejecting valid numeric ("42") and boolean ("true"/"false") values. Updated the filter to coerce and validate instead, with tests covering both JSON and form-encoded cases. * fix: prevent mass-assignment of webhookSecret fields in chatflow create/update Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat:367 | Webhooks - Human Input + Callback Support (#6263) * Added HITL support for webhooks * feat: add async callback URL to webhook trigger Optional Callback URL / Secret on the Start node (or x-callback-url header). Webhook now returns 202 immediately and POSTs SUCCESS, STOPPED (HITL), or ERROR to the callback URL, signed with HMAC-SHA256 when a secret is set. Retries 3x with 0s/3s/6s backoff. * used getErrorMessage for error messages * feat: add object/array body param types and per-option show/hide on d… (#6273) * feat: add webhook secret & HMAC signature verification to webhook trigger Adds server-side webhook secret management (generate/clear/verify) and a UI control in the Start node for configuring the secret, signature header, and signature type (HMAC-SHA256 or plain token). Raw request body is now captured before JSON parsing so HMAC signatures can be verified against the original bytes. Migrations added for all four supported databases. * fix: accept string-coerced numbers and booleans in webhook body type validation application/x-www-form-urlencoded payloads deliver all values as strings, so the strict typeof check was incorrectly rejecting valid numeric ("42") and boolean ("true"/"false") values. Updated the filter to coerce and validate instead, with tests covering both JSON and form-encoded cases. * Added HITL support for webhooks * feat: add async callback URL to webhook trigger Optional Callback URL / Secret on the Start node (or x-callback-url header). Webhook now returns 202 immediately and POSTs SUCCESS, STOPPED (HITL), or ERROR to the callback URL, signed with HMAC-SHA256 when a secret is set. Retries 3x with 0s/3s/6s backoff. * feat: add object/array body param types and per-option show/hide on dropdowns - Add object, array[string/number/boolean/object] as webhook body param types, available when content type is application/json - Extend options fields with show/hide conditions so individual dropdown choices can be hidden based on other param values * fix: prevent SSRF in webhook callback URL Remove the x-callback-url header override that allowed any external caller to control where the server sends POST requests. Callback URL now only comes from the Start node config (authenticated users). Add checkDenyList validation to block callback URLs targeting private networks, cloud metadata endpoints, and loopback addresses. * fix: exclude HTTP method from webhook URL copy Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: enhance webhook functionality in Start node - Added new input options for webhook handling, including input modes (Custom Text, No Input, Full Webhook Payload) and response modes (Synchronous, Asynchronous, Streaming). - Implemented request signature verification and callback URL handling for asynchronous responses. - Updated validation logic for webhook requests, including content type and required headers. - Enhanced tests to cover new webhook features and validation scenarios * adds a generic SSE observer primitive and a webhook listener registry that lets the canvas watch incoming webhook executions live * fix webhook tests by mocking webhook listener registry and updating expectations for chatId in createWebhook function * fix: harden webhook trigger surface against SSRF, secret leakage, and listener data exposure * fix: improve webhook listener reliability with initial heartbeat and logging --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Henry <hzj94@hotmail.com>

GETPOSTPUTPATCHDELETETest Videos Demo
query-params-and-auto-complete.mp4
query-params-and-auto-complete.mp4
body-headers-params-work-and-auto-complete.mp4
body-headers-params-work-and-auto-complete.mp4
method-validation-return-405-if-not-matched.mp4
method-validation-return-405-if-not-matched.mp4
headers-required-not-included-will-400.mp4
headers-required-not-included-will-400.mp4
content-type-validation.mp4
content-type-validation.mp4
Screenshots
HTTP Methods

Content Type

Only included these two to keep it simple for now. Can add further
multipart/form-dataandtext/plainin the futureExpected Headers

Expected Query Params

Test Passes
