Issue
I’m building a custom trigger node that includes an activate()
method intended to register a webhook with a third-party service when a workflow is activated. I’ve set activationMode: 'always'
in the webhook definition, expecting the activate()
method to be called automatically.
However, despite the workflow being activated in the UI and everything else working, the activate()
method is never triggered — no logs, no errors, and no API call is made.
My goal is to ensure that the activate()
method is called during workflow activation so I can programmatically register webhooks with an external API
Error
There is no error message. The method simply doesn’t seem to get invoked. The console.log
and LoggerProxy.info
in activate()
never appear in logs.
Configrations/Settings
This is a custom node; the workflow is just a basic one-node setup that uses the trigger node. But here’s the relevant webhooks
config:
webhooks: [{
name: ‘default’,
httpMethod: ‘POST’,
responseMode: ‘onReceived’,
path: ‘debug-webhook’,
isFullPath: false,
}]
tried with all the below flags as well and activating/deactivating the workflow
activationMode: ‘always’,
restartWebhook: true,
isActive: true,
And here’s my activate()
method:
async activate(this: IHookFunctions): Promise {
console.log(‘activate called’);
LoggerProxy.info(‘activate called’);
const url = this.getNodeWebhookUrl(‘default’);
console.log(‘Webhook URL:’, url);
// Simulated registration to 3rd party
return true;
}
-n8n version:1.89.2
-Database: Postgres
-n8n EXECUTIONS_PROCESS setting default:
-Running n8n via Docker on local system
-Windows 11
— below is complete node code.
</>
import {
INodeType,
INodeTypeDescription,
IWebhookFunctions,
IWebhookResponseData,
ILoadOptionsFunctions,
INodePropertyOptions,
IHookFunctions,
IDataObject,
NodeApiError,
LoggerProxy,
} from 'n8n-workflow';
import { MyCustomNodeClient } from './Utils/Client';
// Single log when module is loaded
LoggerProxy.info('MyCustomNode Webhook: Module loaded');
export class MyCustomNodeWebhook implements INodeType {
constructor() {
LoggerProxy.info('MyCustomNode Webhook: Constructor called');
}
description: INodeTypeDescription = {
displayName: 'MyCustomNode Trigger',
name: 'MyCustomNodeTrigger',
icon: 'file:betterlogo.svg',
group: ['trigger'],
version: 1,
description: 'Handle MyCustomNode webhook events',
defaults: {
name: 'MyCustomNode Trigger',
},
inputs: [],
outputs: ['main'],
credentials: [
{
name: 'myCustomNodeApi',
required: true,
},
],
webhooks: [
{
name: 'default',
httpMethod: 'POST',
responseMode: 'onReceived',
path: 'webhook',
// isActive: true,
// restartWebhook: true,
// activationMode: 'always',
},
],
properties: [
{
displayName: 'Event',
name: 'event',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getWebhookEvents',
},
default: 'order.created',
required: true,
description: 'The event to listen to',
},
{
displayName: 'Additional Fields',
name: 'additionalFields',
type: 'collection',
placeholder: 'Add Field',
default: {},
options: [
{
displayName: 'Description',
name: 'description',
type: 'string',
default: '',
description: 'Description for the webhook',
},
{
displayName: 'Include Metadata',
name: 'includeMetadata',
type: 'boolean',
default: false,
description: 'Whether to include metadata in the webhook payload',
},
{
displayName: 'Headers',
name: 'headers',
placeholder: 'Add Header',
type: 'fixedCollection',
typeOptions: {
multipleValues: true,
},
default: {},
options: [
{
name: 'header',
displayName: 'Header',
values: [
{
displayName: 'Name',
name: 'name',
type: 'string',
default: '',
description: 'Name of the header',
},
{
displayName: 'Value',
name: 'value',
type: 'string',
default: '',
description: 'Value of the header',
},
],
},
],
},
],
},
],
};
// Methods to load options dynamically
methods = {
loadOptions: {
async getWebhookEvents(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
LoggerProxy.info('MyCustomNode Webhook: getWebhookEvents method called');
return [
// Order events
{ name: 'Order Created', value: 'order.created' },
{ name: 'Order Status Changed', value: 'order.status.changed' },
];
},
}
}
// This method is called when the node is activated (when the workflow is activated)
async activate(this: IHookFunctions): Promise<boolean> {
LoggerProxy.info('MyCustomNode Webhook: ACTIVATE method called');
// logic to register the webhook on the 3rd party
return true;
} catch (error) {
LoggerProxy.error('MyCustomNode Webhook: Error in activate method', { error });
throw new NodeApiError(this.getNode(), error);
}
}
async webhook(this: IWebhookFunctions): Promise<IWebhookResponseData> {
LoggerProxy.info('MyCustomNode Webhook: WEBHOOK method called');
try {
const bodyData = this.getBodyData() as IDataObject;
const eventType = this.getNodeParameter('event') as string;
// Optional: Add any data transformation specific to the event type
let processedData: IDataObject = bodyData;
// Example of event-specific processing
if (eventType === 'order.created' /*&& bodyData.order*/) {
// Extract just the order data
//processedData = bodyData.order as IDataObject;
processedData = bodyData;
}
return {
workflowData: [
this.helpers.returnJsonArray(processedData),
],
};
} catch (error) {
LoggerProxy.error('MyCustomNode Webhook node: Error in webhook method', { error });
throw new NodeApiError(this.getNode(), error);
}
}
}
</>