From 2ac4deaafe63acb1fb969dee32423780f579ef5e Mon Sep 17 00:00:00 2001 From: Dheeraj Shrivastav Date: Thu, 5 Mar 2026 00:03:52 +0530 Subject: [PATCH 1/5] feat: implement webhookTriggerNode for receiving incoming HTTP webhooks - Create webhook-trigger.ts with full node definition - Support configurable HTTP methods (GET, POST, PUT) - Implement three authentication types: none, basic auth, and custom headers - Return parsed request body, headers, method, path, query, and timestamp - Mark authentication status in output for downstream validation Co-Authored-By: Claude --- packages/nodes/src/logic/webhook-trigger.ts | 181 ++++++++++++++++++++ 1 file changed, 181 insertions(+) create mode 100644 packages/nodes/src/logic/webhook-trigger.ts diff --git a/packages/nodes/src/logic/webhook-trigger.ts b/packages/nodes/src/logic/webhook-trigger.ts new file mode 100644 index 0000000..c0bde66 --- /dev/null +++ b/packages/nodes/src/logic/webhook-trigger.ts @@ -0,0 +1,181 @@ +import { z } from 'zod' +import { defineNode } from '@jam-nodes/core' + +/** + * Input schema for webhook trigger node + */ +export const WebhookTriggerInputSchema = z.object({ + path: z.string().min(1).regex(/^\//, 'Path must start with /'), + method: z.enum(['GET', 'POST', 'PUT']), + authentication: z + .object({ + type: z.enum(['none', 'basic', 'header']), + credentials: z.record(z.string()).optional(), + }) + .optional(), + responseCode: z.number().int().min(100).max(599).default(200).optional(), + responseData: z.unknown().optional(), +}) + +export type WebhookTriggerInput = z.infer + +/** + * Output schema for webhook trigger node + */ +export const WebhookTriggerOutputSchema = z.object({ + body: z.unknown(), + headers: z.record(z.string()), + method: z.string(), + path: z.string(), + query: z.record(z.string()), + timestamp: z.string(), + authenticated: z.boolean(), +}) + +export type WebhookTriggerOutput = z.infer + +/** + * Webhook trigger node. + * + * A configuration + validation node that processes incoming HTTP webhook payloads. + * The host application is responsible for registering the route and injecting + * the incoming request data into context.variables.webhookRequest before executing. + * + * Supports three authentication types: + * - none: No auth check + * - basic: HTTP Basic Auth via Authorization header + * - header: Custom header key/value validation + * + * @example + * ```typescript + * { + * path: '/webhooks/stripe', + * method: 'POST', + * authentication: { + * type: 'header', + * credentials: { 'x-stripe-signature': 'expected-secret' } + * } + * } + * ``` + */ +export const webhookTriggerNode = defineNode({ + type: 'webhook_trigger', + name: 'Webhook Trigger', + description: 'Receive incoming HTTP webhook payloads with configurable authentication', + category: 'logic', + inputSchema: WebhookTriggerInputSchema, + outputSchema: WebhookTriggerOutputSchema, + estimatedDuration: 0, + capabilities: { + supportsRerun: false, + supportsCancel: false, + }, + executor: async (input, context) => { + try { + // Step 1 — Read incoming request from context + const webhookRequest = context.resolveNestedPath('webhookRequest') as + | { + method?: string + headers?: Record + body?: unknown + path?: string + query?: Record + } + | undefined + + if (!webhookRequest) { + return { + success: false, + error: + 'No webhook request data found in context. Ensure the host application injects webhookRequest into context.variables before executing this node.', + } + } + + // Step 2 — Validate method + if (webhookRequest.method?.toUpperCase() !== input.method) { + return { + success: false, + error: `Method mismatch: expected ${input.method}, received ${webhookRequest.method}`, + } + } + + // Step 3 — Authentication + let authenticated = false + const authType = input.authentication?.type ?? 'none' + + if (authType === 'none') { + authenticated = true + } else if (authType === 'basic') { + const authHeader = webhookRequest.headers?.['authorization'] + if (!authHeader) { + return { + success: false, + error: 'Basic authentication failed: invalid credentials', + } + } + + if (!authHeader.startsWith('Basic ')) { + return { + success: false, + error: 'Basic authentication failed: invalid credentials', + } + } + + const base64 = authHeader.slice(6) + const decoded = Buffer.from(base64, 'base64').toString('utf-8') + const colonIndex = decoded.indexOf(':') + if (colonIndex === -1) { + return { + success: false, + error: 'Basic authentication failed: invalid credentials', + } + } + + const username = decoded.slice(0, colonIndex) + const password = decoded.slice(colonIndex + 1) + const expectedUsername = input.authentication?.credentials?.['username'] + const expectedPassword = input.authentication?.credentials?.['password'] + + if (username !== expectedUsername || password !== expectedPassword) { + return { + success: false, + error: 'Basic authentication failed: invalid credentials', + } + } + + authenticated = true + } else if (authType === 'header') { + const credentials = input.authentication?.credentials ?? {} + for (const [key, expectedValue] of Object.entries(credentials)) { + const actualValue = webhookRequest.headers?.[key.toLowerCase()] + if (actualValue !== expectedValue) { + return { + success: false, + error: 'Header authentication failed: missing or invalid header', + } + } + } + authenticated = true + } + + // Step 4 — Return success + return { + success: true, + output: { + body: webhookRequest.body ?? null, + headers: webhookRequest.headers ?? {}, + method: webhookRequest.method!, + path: webhookRequest.path ?? input.path, + query: webhookRequest.query ?? {}, + timestamp: new Date().toISOString(), + authenticated, + }, + } + } catch (error) { + return { + success: false, + error: error instanceof Error ? error.message : 'Unknown error', + } + } + }, +}) From 2c37d6488eb331dd80d434ec6462d5227c46a1d4 Mon Sep 17 00:00:00 2001 From: Dheeraj Shrivastav Date: Thu, 5 Mar 2026 00:05:42 +0530 Subject: [PATCH 2/5] test: add comprehensive tests for webhookTriggerNode - Test node metadata (type, category, capabilities) - Validate input schema constraints (path format, HTTP methods, auth types) - Test executor error handling (missing request, method mismatch) - Test authentication: none, basic auth (base64 decode), header validation - Test output schema and all fields (body, headers, method, path, query, timestamp) - Total: 28 test cases across 8 describe blocks Co-Authored-By: Claude --- .../logic/__tests__/webhook-trigger.test.ts | 468 ++++++++++++++++++ 1 file changed, 468 insertions(+) create mode 100644 packages/nodes/src/logic/__tests__/webhook-trigger.test.ts diff --git a/packages/nodes/src/logic/__tests__/webhook-trigger.test.ts b/packages/nodes/src/logic/__tests__/webhook-trigger.test.ts new file mode 100644 index 0000000..5447a2e --- /dev/null +++ b/packages/nodes/src/logic/__tests__/webhook-trigger.test.ts @@ -0,0 +1,468 @@ +import { describe, it, expect } from 'vitest' +import { + webhookTriggerNode, + WebhookTriggerInputSchema, + WebhookTriggerOutputSchema, +} from '../webhook-trigger.js' + +/** + * Creates a mock NodeExecutionContext with an optional webhookRequest injected. + */ +function makeContext(webhookRequest?: unknown) { + const variables = { webhookRequest } + return { + userId: 'test-user', + workflowExecutionId: 'test-run', + variables, + resolveNestedPath: (path: string) => + path.split('.').reduce((obj: unknown, key: string) => { + if (obj && typeof obj === 'object') { + return (obj as Record)[key] + } + return undefined + }, variables as unknown), + credentials: {}, + } +} + +// --------------------------------------------------------------------------- +// Metadata +// --------------------------------------------------------------------------- + +describe('webhookTriggerNode - metadata', () => { + it('should have type webhook_trigger', () => { + expect(webhookTriggerNode.type).toBe('webhook_trigger') + }) + + it('should have category logic', () => { + expect(webhookTriggerNode.category).toBe('logic') + }) + + it('should not support rerun', () => { + expect(webhookTriggerNode.capabilities?.supportsRerun).toBe(false) + }) + + it('should not support cancel', () => { + expect(webhookTriggerNode.capabilities?.supportsCancel).toBe(false) + }) +}) + +// --------------------------------------------------------------------------- +// Input schema validation +// --------------------------------------------------------------------------- + +describe('webhookTriggerNode - input schema validation', () => { + it('should accept minimal valid input (path + method)', () => { + const result = WebhookTriggerInputSchema.safeParse({ + path: '/webhooks/test', + method: 'POST', + }) + expect(result.success).toBe(true) + }) + + it('should reject a path that does not start with /', () => { + const result = WebhookTriggerInputSchema.safeParse({ + path: 'webhooks/test', + method: 'POST', + }) + expect(result.success).toBe(false) + }) + + it('should reject an empty path', () => { + const result = WebhookTriggerInputSchema.safeParse({ + path: '', + method: 'POST', + }) + expect(result.success).toBe(false) + }) + + it('should reject an invalid method like DELETE', () => { + const result = WebhookTriggerInputSchema.safeParse({ + path: '/webhooks/test', + method: 'DELETE', + }) + expect(result.success).toBe(false) + }) + + it('should accept optional responseCode between 100 and 599', () => { + const result = WebhookTriggerInputSchema.safeParse({ + path: '/webhooks/test', + method: 'GET', + responseCode: 204, + }) + expect(result.success).toBe(true) + }) + + it('should reject responseCode 99', () => { + const result = WebhookTriggerInputSchema.safeParse({ + path: '/webhooks/test', + method: 'GET', + responseCode: 99, + }) + expect(result.success).toBe(false) + }) + + it('should reject responseCode 600', () => { + const result = WebhookTriggerInputSchema.safeParse({ + path: '/webhooks/test', + method: 'GET', + responseCode: 600, + }) + expect(result.success).toBe(false) + }) + + it('should accept authentication type none with no credentials', () => { + const result = WebhookTriggerInputSchema.safeParse({ + path: '/webhooks/test', + method: 'POST', + authentication: { type: 'none' }, + }) + expect(result.success).toBe(true) + }) + + it('should accept authentication type basic with credentials', () => { + const result = WebhookTriggerInputSchema.safeParse({ + path: '/webhooks/test', + method: 'POST', + authentication: { + type: 'basic', + credentials: { username: 'admin', password: 'secret' }, + }, + }) + expect(result.success).toBe(true) + }) + + it('should accept authentication type header with credentials', () => { + const result = WebhookTriggerInputSchema.safeParse({ + path: '/webhooks/test', + method: 'POST', + authentication: { + type: 'header', + credentials: { 'x-api-key': 'token123' }, + }, + }) + expect(result.success).toBe(true) + }) +}) + +// --------------------------------------------------------------------------- +// Executor: no webhookRequest in context +// --------------------------------------------------------------------------- + +describe('webhookTriggerNode - executor: no webhookRequest in context', () => { + it('should return success false with descriptive error when webhookRequest is missing', async () => { + const context = makeContext(undefined) + const result = await webhookTriggerNode.executor( + { path: '/test', method: 'POST' }, + context as never, + ) + expect(result.success).toBe(false) + if (!result.success) { + expect(result.error).toContain('No webhook request data found') + } + }) +}) + +// --------------------------------------------------------------------------- +// Executor: method mismatch +// --------------------------------------------------------------------------- + +describe('webhookTriggerNode - executor: method mismatch', () => { + it('should return success false when incoming method does not match configured method', async () => { + const context = makeContext({ + method: 'GET', + headers: {}, + body: null, + path: '/test', + query: {}, + }) + const result = await webhookTriggerNode.executor( + { path: '/test', method: 'POST' }, + context as never, + ) + expect(result.success).toBe(false) + if (!result.success) { + expect(result.error).toContain('Method mismatch') + } + }) +}) + +// --------------------------------------------------------------------------- +// Executor: auth type none +// --------------------------------------------------------------------------- + +describe('webhookTriggerNode - executor: auth type none', () => { + const baseRequest = { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: { event: 'push' }, + path: '/webhooks/github', + query: { ref: 'main' }, + } + + it('should return success true and authenticated true regardless of headers', async () => { + const context = makeContext(baseRequest) + const result = await webhookTriggerNode.executor( + { path: '/webhooks/github', method: 'POST', authentication: { type: 'none' } }, + context as never, + ) + expect(result.success).toBe(true) + if (result.success) { + expect(result.output.authenticated).toBe(true) + } + }) + + it('should include body, headers, method, path, query, timestamp, authenticated in output', async () => { + const context = makeContext(baseRequest) + const result = await webhookTriggerNode.executor( + { path: '/webhooks/github', method: 'POST' }, + context as never, + ) + expect(result.success).toBe(true) + if (result.success) { + expect(result.output.body).toEqual({ event: 'push' }) + expect(result.output.headers).toEqual({ 'content-type': 'application/json' }) + expect(result.output.method).toBe('POST') + expect(result.output.path).toBe('/webhooks/github') + expect(result.output.query).toEqual({ ref: 'main' }) + expect(typeof result.output.timestamp).toBe('string') + expect(result.output.authenticated).toBe(true) + } + }) +}) + +// --------------------------------------------------------------------------- +// Executor: auth type basic +// --------------------------------------------------------------------------- + +describe('webhookTriggerNode - executor: auth type basic', () => { + const correctCreds = Buffer.from('admin:secret').toString('base64') + + function makeBasicRequest(authHeader?: string) { + return { + method: 'POST', + headers: authHeader ? { authorization: authHeader } : {}, + body: { data: 1 }, + path: '/secure', + query: {}, + } + } + + it('should return success true when Authorization header has correct base64 credentials', async () => { + const context = makeContext(makeBasicRequest(`Basic ${correctCreds}`)) + const result = await webhookTriggerNode.executor( + { + path: '/secure', + method: 'POST', + authentication: { + type: 'basic', + credentials: { username: 'admin', password: 'secret' }, + }, + }, + context as never, + ) + expect(result.success).toBe(true) + if (result.success) { + expect(result.output.authenticated).toBe(true) + } + }) + + it('should return success false when credentials are wrong', async () => { + const wrongCreds = Buffer.from('admin:wrong').toString('base64') + const context = makeContext(makeBasicRequest(`Basic ${wrongCreds}`)) + const result = await webhookTriggerNode.executor( + { + path: '/secure', + method: 'POST', + authentication: { + type: 'basic', + credentials: { username: 'admin', password: 'secret' }, + }, + }, + context as never, + ) + expect(result.success).toBe(false) + if (!result.success) { + expect(result.error).toContain('Basic authentication failed') + } + }) + + it('should return success false when Authorization header is missing', async () => { + const context = makeContext(makeBasicRequest()) + const result = await webhookTriggerNode.executor( + { + path: '/secure', + method: 'POST', + authentication: { + type: 'basic', + credentials: { username: 'admin', password: 'secret' }, + }, + }, + context as never, + ) + expect(result.success).toBe(false) + if (!result.success) { + expect(result.error).toContain('Basic authentication failed') + } + }) + + it('should return success false when Authorization header is not Basic type', async () => { + const context = makeContext(makeBasicRequest('Bearer some-token')) + const result = await webhookTriggerNode.executor( + { + path: '/secure', + method: 'POST', + authentication: { + type: 'basic', + credentials: { username: 'admin', password: 'secret' }, + }, + }, + context as never, + ) + expect(result.success).toBe(false) + if (!result.success) { + expect(result.error).toContain('Basic authentication failed') + } + }) +}) + +// --------------------------------------------------------------------------- +// Executor: auth type header +// --------------------------------------------------------------------------- + +describe('webhookTriggerNode - executor: auth type header', () => { + it('should return success true when all required headers are present and match', async () => { + const context = makeContext({ + method: 'POST', + headers: { 'x-api-key': 'secret123', 'content-type': 'application/json' }, + body: null, + path: '/hook', + query: {}, + }) + const result = await webhookTriggerNode.executor( + { + path: '/hook', + method: 'POST', + authentication: { + type: 'header', + credentials: { 'x-api-key': 'secret123' }, + }, + }, + context as never, + ) + expect(result.success).toBe(true) + if (result.success) { + expect(result.output.authenticated).toBe(true) + } + }) + + it('should return success false when a required header is missing', async () => { + const context = makeContext({ + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: null, + path: '/hook', + query: {}, + }) + const result = await webhookTriggerNode.executor( + { + path: '/hook', + method: 'POST', + authentication: { + type: 'header', + credentials: { 'x-api-key': 'secret123' }, + }, + }, + context as never, + ) + expect(result.success).toBe(false) + if (!result.success) { + expect(result.error).toContain('Header authentication failed') + } + }) + + it('should return success false when a required header has the wrong value', async () => { + const context = makeContext({ + method: 'POST', + headers: { 'x-api-key': 'wrong-value' }, + body: null, + path: '/hook', + query: {}, + }) + const result = await webhookTriggerNode.executor( + { + path: '/hook', + method: 'POST', + authentication: { + type: 'header', + credentials: { 'x-api-key': 'secret123' }, + }, + }, + context as never, + ) + expect(result.success).toBe(false) + if (!result.success) { + expect(result.error).toContain('Header authentication failed') + } + }) + + it('should match headers case-insensitively', async () => { + // The executor lowercases the credential key when looking up in request headers. + // The incoming request headers should already be lowercase (per convention). + const context = makeContext({ + method: 'POST', + headers: { 'x-custom-token': 'token-abc' }, + body: null, + path: '/hook', + query: {}, + }) + const result = await webhookTriggerNode.executor( + { + path: '/hook', + method: 'POST', + authentication: { + type: 'header', + // Credential key provided in mixed case — executor must lowercase it + credentials: { 'X-Custom-Token': 'token-abc' }, + }, + }, + context as never, + ) + expect(result.success).toBe(true) + if (result.success) { + expect(result.output.authenticated).toBe(true) + } + }) +}) + +// --------------------------------------------------------------------------- +// Output schema +// --------------------------------------------------------------------------- + +describe('webhookTriggerNode - output schema', () => { + it('should validate a well-formed output successfully', () => { + const result = WebhookTriggerOutputSchema.safeParse({ + body: { event: 'push' }, + headers: { 'content-type': 'application/json' }, + method: 'POST', + path: '/webhooks/github', + query: {}, + timestamp: new Date().toISOString(), + authenticated: true, + }) + expect(result.success).toBe(true) + }) + + it('should reject output missing authenticated field', () => { + const result = WebhookTriggerOutputSchema.safeParse({ + body: null, + headers: {}, + method: 'POST', + path: '/hook', + query: {}, + timestamp: new Date().toISOString(), + // authenticated is missing + }) + expect(result.success).toBe(false) + }) +}) From 3936c75d44f285f29960dc761efa062c079f7ab3 Mon Sep 17 00:00:00 2001 From: Dheeraj Shrivastav Date: Thu, 5 Mar 2026 00:06:32 +0530 Subject: [PATCH 3/5] refactor: export webhookTriggerNode from logic module - Add named export for webhookTriggerNode - Export WebhookTriggerInputSchema and WebhookTriggerOutputSchema - Export TypeScript types WebhookTriggerInput and WebhookTriggerOutput Co-Authored-By: Claude --- packages/nodes/src/logic/index.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/nodes/src/logic/index.ts b/packages/nodes/src/logic/index.ts index 30b27e9..a9d4792 100644 --- a/packages/nodes/src/logic/index.ts +++ b/packages/nodes/src/logic/index.ts @@ -19,3 +19,7 @@ export { EndInputSchema, EndOutputSchema } from './end.js'; export { delayNode } from './delay.js'; export type { DelayInput, DelayOutput } from './delay.js'; export { DelayInputSchema, DelayOutputSchema } from './delay.js'; + +export { webhookTriggerNode } from './webhook-trigger.js'; +export { WebhookTriggerInputSchema, WebhookTriggerOutputSchema } from './webhook-trigger.js'; +export type { WebhookTriggerInput, WebhookTriggerOutput } from './webhook-trigger.js'; From 51b043cf18fb2ab00f50149292e5108384dd011f Mon Sep 17 00:00:00 2001 From: Dheeraj Shrivastav Date: Thu, 5 Mar 2026 00:07:22 +0530 Subject: [PATCH 4/5] refactor: register webhookTriggerNode in main package exports - Add webhookTriggerNode, WebhookTriggerInputSchema, WebhookTriggerOutputSchema to named exports from logic module - Add WebhookTriggerInput, WebhookTriggerOutput type exports - Import webhookTriggerNode for builtin nodes registry - Add webhookTriggerNode to builtInNodes array for auto-registration Co-Authored-By: Claude --- packages/nodes/src/index.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/nodes/src/index.ts b/packages/nodes/src/index.ts index 5c2de45..4f76041 100644 --- a/packages/nodes/src/index.ts +++ b/packages/nodes/src/index.ts @@ -3,6 +3,7 @@ export { conditionalNode, endNode, delayNode, + webhookTriggerNode, ConditionalInputSchema, ConditionalOutputSchema, ConditionSchema, @@ -11,6 +12,8 @@ export { EndOutputSchema, DelayInputSchema, DelayOutputSchema, + WebhookTriggerInputSchema, + WebhookTriggerOutputSchema, } from './logic/index.js' export type { @@ -22,6 +25,8 @@ export type { EndOutput, DelayInput, DelayOutput, + WebhookTriggerInput, + WebhookTriggerOutput, } from './logic/index.js' // Transform nodes @@ -268,6 +273,7 @@ export type { import { conditionalNode } from './logic/index.js' import { endNode } from './logic/index.js' import { delayNode } from './logic/index.js' +import { webhookTriggerNode } from './logic/index.js' import { mapNode, filterNode, sortNode } from './transform/index.js' import { httpRequestNode, breadNode } from './examples/index.js' import { @@ -313,6 +319,7 @@ export const builtInNodes = [ conditionalNode, endNode, delayNode, + webhookTriggerNode, // Transform mapNode, filterNode, From d0bf50944294edcbc0a2424c7639d000e43d481d Mon Sep 17 00:00:00 2001 From: Dheeraj Shrivastav Date: Thu, 5 Mar 2026 00:43:43 +0530 Subject: [PATCH 5/5] fix: surface responseCode and responseData in executor output Previously these fields were only in the input schema, so the host app had to hold onto the config separately to know what HTTP response to send back. Now the executor passes them through in the output, making the result self-contained. - Add responseCode and responseData to WebhookTriggerOutputSchema - Return input.responseCode (defaulting to 200) and input.responseData from the executor - Add 3 new tests: missing responseCode rejected, responseCode and responseData present in output, default responseCode is 200 Co-Authored-By: Claude --- .../logic/__tests__/webhook-trigger.test.ts | 59 +++++++++++++++++++ packages/nodes/src/logic/webhook-trigger.ts | 4 ++ 2 files changed, 63 insertions(+) diff --git a/packages/nodes/src/logic/__tests__/webhook-trigger.test.ts b/packages/nodes/src/logic/__tests__/webhook-trigger.test.ts index 5447a2e..fa851ca 100644 --- a/packages/nodes/src/logic/__tests__/webhook-trigger.test.ts +++ b/packages/nodes/src/logic/__tests__/webhook-trigger.test.ts @@ -449,6 +449,8 @@ describe('webhookTriggerNode - output schema', () => { query: {}, timestamp: new Date().toISOString(), authenticated: true, + responseCode: 200, + responseData: { status: 'ok' }, }) expect(result.success).toBe(true) }) @@ -461,8 +463,65 @@ describe('webhookTriggerNode - output schema', () => { path: '/hook', query: {}, timestamp: new Date().toISOString(), + responseCode: 200, // authenticated is missing }) expect(result.success).toBe(false) }) + + it('should reject output missing responseCode field', () => { + const result = WebhookTriggerOutputSchema.safeParse({ + body: null, + headers: {}, + method: 'POST', + path: '/hook', + query: {}, + timestamp: new Date().toISOString(), + authenticated: true, + // responseCode is missing + }) + expect(result.success).toBe(false) + }) + + it('should include responseCode and responseData in executor output', async () => { + const context = makeContext({ + method: 'POST', + path: '/hook', + headers: {}, + body: null, + query: {}, + }) + const result = await webhookTriggerNode.executor( + { + path: '/hook', + method: 'POST', + responseCode: 201, + responseData: { received: true }, + }, + context as never, + ) + expect(result.success).toBe(true) + if (result.success) { + expect(result.output.responseCode).toBe(201) + expect(result.output.responseData).toEqual({ received: true }) + } + }) + + it('should default responseCode to 200 when not configured', async () => { + const context = makeContext({ + method: 'POST', + path: '/hook', + headers: {}, + body: null, + query: {}, + }) + const result = await webhookTriggerNode.executor( + { path: '/hook', method: 'POST' }, + context as never, + ) + expect(result.success).toBe(true) + if (result.success) { + expect(result.output.responseCode).toBe(200) + } + }) }) diff --git a/packages/nodes/src/logic/webhook-trigger.ts b/packages/nodes/src/logic/webhook-trigger.ts index c0bde66..9811a31 100644 --- a/packages/nodes/src/logic/webhook-trigger.ts +++ b/packages/nodes/src/logic/webhook-trigger.ts @@ -30,6 +30,8 @@ export const WebhookTriggerOutputSchema = z.object({ query: z.record(z.string()), timestamp: z.string(), authenticated: z.boolean(), + responseCode: z.number().int().min(100).max(599), + responseData: z.unknown().optional(), }) export type WebhookTriggerOutput = z.infer @@ -169,6 +171,8 @@ export const webhookTriggerNode = defineNode({ query: webhookRequest.query ?? {}, timestamp: new Date().toISOString(), authenticated, + responseCode: input.responseCode ?? 200, + responseData: input.responseData, }, } } catch (error) {