Hello @ppz,
I think that yur custom node is trying to build the webhook URL using:
const baseUrl = this.additionalData.instanceBaseUrl;
const webhookUrl = `${baseUrl}webhook/${webhookId}`;
If instanceBaseUrl
(WEBHOOK_URL
) isnât set, baseUrl
is empty, so the generated URL is invalid and fails.
Try to set WEBHOOK_URL
If self-hosted or in Docker, add:
environment:
- WEBHOOK_URL=https://your-domain.com/
This ensures instance base Url is populated correctly.
Let me know if my directions are clear because your question is a little vague and I could say inaccuracies.
Thank you very much for your help. I extract the source code from the official website and run and modify it locally. I created a node similar to a form node and replaced the function of generating form nodes with a custom editor. This editor returns a piece of html code containing the form. The problem Iâm encountering now is:
The link generated by the webhook trigger node serves as the entry point. Through this link, a link redirected to this custom node is implemented, but when the link is opened, it shows as unregistered
Judging from the content I printed, instanceBaseUrl has a valueďźAnd Webhooks also have value
Perhaps it canât be spliced like this? Or is my overall direction of modification wrong?
Your URL logic is correct, but the webhook only works if itâs registered with n8n, which requires proper webhookMethods
in your custom node and an activated workflow or a test event.
- Implement
webhookMethods
in your custom node:
checkExists()
should confirm registration.create()
must callthis.getNodeWebhookUrl('default')
to register the webhook.delete()
cleans up on deactivation.
- Avoid building URL manually, always use
this.getNodeWebhookUrl('default')
Ok. Referring to your solution, I added some new content, but Iâm not sure if itâs correct. After opening it through the webhook link, the execute method was directly executed. I donât know how to call the methods in webhoolMethods. Looking forward to your reply. Thank you
When I use this.getNodeWebhookUrl(âdefaultâ), an error will occur. this.getNodeWebhookUrl is not a function. I hope to get your reply. Thank you
Youâre calling this.getNodeWebhookUrl('default')
inside the execute()
method. That method is only available in the webhookMethods
context (like inside create
, checkExists
, delete
), not in execute()
.
ToTry to move any usage of getNodeWebhookUrl
to the create()
method. The execute()
function should only handle the incoming webhook request.
However, the âwebhookMethodsâ method is not set in my node. I have posted my entire code here and hope you can help me point out the areas that need to be modified
import type {
IExecuteFunctions, IHookFunctions,
INodeExecutionData,
INodeProperties,
INodeTypeDescription,
IWebhookFunctions,
IWebhookResponseData,
NodeTypeAndVersion,
} from 'n8n-workflow';
import {
INodeType,
updateDisplayOptions,
NodeConnectionTypes,
} from 'n8n-workflow';
import { configureWaitTillDate } from '../../utils/sendAndWait/configureWaitTillDate.util';
import { limitWaitTimeProperties } from '../../utils/sendAndWait/descriptions';
import {placeholder} from "./placeholder";
const waitTimeProperties: INodeProperties[] = [
{
displayName: 'Limit Wait Time',
name: 'limitWaitTime',
type: 'boolean',
default: false,
description:
'Whether to limit the time this node should wait for a user response before execution resumes',
},
...updateDisplayOptions(
{
show: {
limitWaitTime: [true],
},
},
limitWaitTimeProperties,
),
];
const pageProperties = updateDisplayOptions(
{
show: {
operation: ['page'],
},
},
[
{
displayName: 'HTML Template',
name: 'html',
type: 'string',
typeOptions: {
editor: 'dynamicFormEditor',
},
default: placeholder,
noDataExpression: true,
description: 'HTML template to render using the visual form editor',
},
...waitTimeProperties,
],
);
export class DragForm implements INodeType {
nodeInputData: INodeExecutionData[] = [];
description: INodeTypeDescription = {
displayName: 'DragForm',
name: 'DragForm',
icon: { light: 'file:dragForm.svg', dark: 'file:dragForm.svg' },
group: ['input'],
version: 1,
description: 'Generate webforms in xsjx and pass their responses to the workflow',
defaults: {
name: 'DragForm',
},
inputs: [NodeConnectionTypes.Main],
outputs: [NodeConnectionTypes.Main],
webhooks: [
{
name: 'default',
httpMethod: 'GET',
responseMode: 'onReceived',
path: '',
restartWebhook: true,
isFullPath: true,
nodeType: 'dragForm',
},
{
name: 'default',
httpMethod: 'POST',
responseMode: 'responseNode',
path: '',
restartWebhook: true,
isFullPath: true,
nodeType: 'dragForm',
},
],
webhookMethods: {
default: {
checkExists:this.checkExists,
create:this.create,
delete:this.delete,
},
},
properties: [
{
displayName: 'Page Type',
name: 'operation',
type: 'options',
default: 'page',
noDataExpression: true,
options: [
{
name: 'Next Form Page',
value: 'page',
},
],
},
...pageProperties,
],
};
async webhook(context: IWebhookFunctions): Promise<IWebhookResponseData> {
const request = context.getRequestObject();
const response = context.getResponseObject();
if (request.method === 'GET') {
const inputItems = context.evaluateExpression('{{ $input.all() }}') as Array<{
json: { formUrl: string; html: string };
}>;
const html = inputItems[0]?.json?.html || '<h1>No HTML found</h1>';
response.writeHead(200, { 'Content-Type': 'text/html' });
response.end(html);
return {
webhookResponse: { status: 200 },
};
}
const operation = context.getNodeParameter('operation', '') as string;
const parentNodes = context.getParentNodes(context.getNode().name);
const trigger = parentNodes.find(
(node) => node.type === 'n8n-nodes-base.webhook',
) as NodeTypeAndVersion;
const mode = context.evaluateExpression(`{{ $('${trigger?.name}').first().json.formMode }}`) as
| 'test'
| 'production';
const defineForm = context.getNodeParameter('html', false) as string;
const method = context.getRequestObject().method;
if (method === 'GET') {
console.log('GET Function');
}
return {
webhookResponse: { status: 200 },
workflowData: [[returnItem]],
};
}
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const operation = this.getNodeParameter('operation', 0);
const html = this.getNodeParameter('html', 0);
const items = this.getInputData();
const baseUrl = this.additionalData.instanceBaseUrl;
const webhookId = this.getNode().webhookId;
const webhookUrl = `${baseUrl}webhook/${webhookId}`;
this.sendResponse({
headers: {
location: webhookUrl,
},
statusCode: 307,
});
const parentNodes = this.getParentNodes(this.getNode().name);
const childNodes = this.getChildNodes(this.getNode().name);
const waitTill = configureWaitTillDate(this, 'root');
await this.putExecutionToWait(waitTill);
return [[]];
}
async checkExists(this: IHookFunctions): Promise<boolean> {
const webhookUrl = this.getNodeWebhookUrl('default');
const exists = await this.getWebhookFromDatabase(webhookUrl, 'GET');
return !!exists;
}
async create(this: IHookFunctions): Promise<boolean> {
const baseUrl = this.additionalData.instanceBaseUrl;
const webhookId = this.getNode().webhookId;
const webhookUrl = `${baseUrl}webhook/${webhookId}`;
await this.addWebhookToDatabase(webhookUrl, 'GET', 'dragForm', true);
await this.addWebhookToDatabase(webhookUrl, 'POST', 'dragForm', true);
return true;
}
async delete(this: IHookFunctions): Promise<boolean> {
const webhookUrl = this.getNodeWebhookUrl('default');
await this.removeWebhookFromDatabase(webhookUrl, 'GET');
return true;
}
}
The WebflowTrigger example shows how to use getNodeWebhookUrl
inside create(), not in your execute() method.
Without these webhookMethods, n8n never actually registers your webhook, so even valid URLs will return unregistered.
Try with this please:
webhookMethods = {
default: {
async checkExists(this: IHookFunctions): Promise<boolean> {
// logic to verify registration state
},
async create(this: IHookFunctions): Promise<boolean> {
const url = this.getNodeWebhookUrl('default');
// logic to register the webhook with your service and store the webhookId in static data
return true;
},
async delete(this: IHookFunctions): Promise<boolean> {
// unregister the webhook using stored webhookId
return true;
},
},
};
Thank you very much. I referred to the WebflowTrigger example above and successfully implemented the invocation of these methods. However, I am now encountering another problem. I have successfully registered the webhookUrl. By const allWebhooks = await this. WebhookRepository. The find () query to all registered webhookUrl, my custom node webhook methods included, But there was a problem when opening. Error in handling webhook request GET /webhook/524c3727-8b97-416f-988a-3c6d266a5bb4: Cannot read properties of undefined (reading ânodeâ). Why is that
This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.