Custom Webhook Node: Issues returning response to caller

Problem Description :

I am developing a custom trigger/webhook node to replace a repetitive 3-node pattern I use in my workflows. My goal is to consolidate logic into a single node for better maintainability.

Current Workflow Pattern:

  • Webhook Trigger: (Method: POST, Response Mode: ‘Respond to Webhook Node’).
  • Code Node: Validates input, transforms data, and generates a response object.
  • Respond to Webhook Node: Sends the final data back to the caller.

The Goal

I want to move the “Code Node” logic directly into the webhook() method of a custom node so that the node itself handles the request, processes the data, and returns the response in one step.

Implementation Attempt:

export class MyWebhook implements INodeType {
    description: INodeTypeDescription = {
        group: ['trigger'],
  // code ommited
        inputs: [],
        outputs: ['main'],
        webhooks: [
            {
                name: 'default',
                httpMethod: 'POST',
				responseMode: 'onReceived',

                isFullPath: true,
                path: '={{ `mywehook/${$parameter["path"]}`}}',
            },
        ],
        properties: [
            {
                displayName: 'Path',
                name: 'path',
                type: 'string',
                default: '',
                placeholder: '',
                required: true,
                description: 'The URL path to listen on',
            },
        ],
    };

    async webhook(this: IWebhookFunctions): Promise<IWebhookResponseData> {
        const bodyData = this.getBodyData()
		const responseData = ... builds response;
        return {
            workflowData: [[{
                json: {
                    params: bodyData,
                },
            }]],
			webhookResponse: responseData,
            noWebhookResponse: false,
        }
    }
}

The Issue:

Although the webhook triggers correctly and the workflow starts, I cannot get the node to return the responseData to the HTTP caller. The caller usually receives a default “Workflow started” message or a timeout, but not the webhookResponse data.

I have tried toggling responseMode and noWebhookResponse in several combinations, but without success.

Question:

What is the correct configuration in INodeTypeDescription and the webhook() return object to ensure the caller receives the custom data immediately?

@danilolr the issue is with your responseMode setting. You’re using 'onReceived' but trying to return custom response data, Change your webhook configuration to use responseMode: 'lastNode':

webhooks: [
    {
        name: 'default',
        httpMethod: 'POST',
        responseMode: 'lastNode', // Changed from 'onReceived'
        isFullPath: true,
        path: '={{ `mywehook/${$parameter["path"]}`}}',
    },
],

Does this solve your issue?

2 Likes

@JohnHalex thanks for your help.

But this do not solve the issue.

I need the response right after my custom node is executed.
After my component is executed there are some long operations. The caller must receive the return right after the initialization.

This is what I have today (the green area is what must be inside the component) :

Ah, I understand now, you want to respond immediately to the caller, then continue with long operations asynchronously. The problem is that in a custom trigger node, you can’t easily split the execution into “respond now” and “continue processing” because trigger nodes are the starting point of the workflow.

Instead of making a custom trigger node, create a custom regular node that:

  1. Receives data from a webhook trigger

  2. Processes and validates

  3. Passes data to “Respond to Webhook”

  4. Continues execution after response

But since you want it all in ONE node, here’s the better approach:

Create a custom node with 2 outputs:

  • Output 1: Goes to “Respond to Webhook” (immediate)

  • Output 2: Goes to long operations (async)

export class MyCustomNode implements INodeType {
    description: INodeTypeDescription = {
        displayName: 'My Custom Node',
        name: 'myCustomNode',
        group: ['transform'],
        version: 1,
        description: 'Process and split execution',
        defaults: {
            name: 'My Custom Node',
        },
        inputs: ['main'],
        outputs: ['main', 'main'], // Two outputs!
        outputNames: ['Response', 'Continue Processing'],
        properties: [
            // Your parameters
        ],
    };

    async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
        const items = this.getInputData();
        const returnData: INodeExecutionData[][] = [[], []];

        for (let i = 0; i < items.length; i++) {
            const item = items[i];
            
            // Your validation/transformation logic
            const processedData = {
                ...item.json,
                myNewField: 1,
                timestamp: new Date().toISOString(),
            };

            // Output 0: Immediate response data
            returnData[0].push({
                json: {
                    status: 'received',
                    id: processedData.id,
                },
            });

            // Output 1: Full data for continued processing
            returnData[1].push({
                json: processedData,
            });
        }

        return returnData;
    }
}

Actually, you CAN do this in a custom webhook trigger by using responseMode: 'onReceived' with custom response data:

export class MyWebhook implements INodeType {
    description: INodeTypeDescription = {
        displayName: 'My Webhook',
        name: 'myWebhook',
        group: ['trigger'],
        version: 1,
        description: 'Webhook with immediate response',
        defaults: {
            name: 'My Webhook',
        },
        inputs: [],
        outputs: ['main'],
        webhooks: [
            {
                name: 'default',
                httpMethod: 'POST',
                responseMode: 'onReceived', // Respond immediately
                responseData: 'firstEntryJson', // Return first item's JSON
                path: 'my-webhook',
            },
        ],
        properties: [],
    };

    async webhook(this: IWebhookFunctions): Promise<IWebhookResponseData> {
        const bodyData = this.getBodyData();
        
        // Your validation/transformation
        const processedData = {
            ...bodyData,
            myNewField: 1,
        };

        // First item = immediate response (sent to caller)
        // Second item = continues to long operations
        return {
            workflowData: [
                [
                    {
                        json: {
                            status: 'received', // This gets returned immediately
                            id: processedData.id,
                        },
                    },
                    {
                        json: processedData, // This continues processing
                    },
                ],
            ],
        };
    }
}

Try this and let me know!

2 Likes