Custom trigger error - cannot read properties of undefined (reading 'push')

Describe the issue/error/question

Hello,

I created a custom trigger node for the Apaleo API.

If I run the trigger with n8n start --tunnel and use

const webhookUrl = this.getNodeWebhookUrl('default') as string

The subscription is created, I can see the webhookId.

The problem is that it throws this error: “*Cannot read properties of undefined (reading ‘push’)” short after clicking “Listen for event”. I have just one array in my trigger node code where I push events for the subscription field and made sure the error is not from there.

I tried to hardcode the const webhookUrl in the checkExists and create methods with URL from webhook.site, using n8n start. In that case, no error is thrown but the callback doesn’t work - the webhook.site receives the event from Apaleo (ex: creation) but n8n doesn’t return it in the workflow. Like it’s not listening to events received on that URL although subscription was created and I can see webhookId.

Please let me know if you need more details to help me solve this issue.

Thank you in advance! :slight_smile:

Please share the workflow

Here are my logs:

Share the output returned by the last node

image

Information on your n8n setup

  • n8n version: 0.209.4
  • Running n8n via [Docker, npm, n8n.cloud, desktop app]: tunnel

Hey @alexnemes,

It would be handy to see the code for your node, It sounds like the issue is in there somewhere.

Hey @Jon, here it is:

ApaleoTrigger.node.ts:

import {
	IDataObject,
	IHookFunctions,
	INodeType,
	INodeTypeDescription,
	IWebhookFunctions,
	IWebhookResponseData,
} from 'n8n-workflow';

import { ApaleoWebhookApi, ParameterExtraction } from './GenericFunctions';

export class ApaleoTrigger implements INodeType {
	description: INodeTypeDescription = {
		credentials: [
			{
				name: 'apaleoOAuth2Api',
				required: true,
			},
		],
		displayName: 'Apaleo Trigger',
		defaults: {
			name: 'Apaleo Trigger',
		},
		description: 'Starts the workflow when Apaleo events occur.',
		group: ['trigger'],
		icon: 'file:apaleo.svg',
		inputs: [],
		name: 'apaleoTrigger',
		outputs: ['main'],
		version: 1,
		webhooks: [
			{
				name: 'default',
				httpMethod: 'POST',
				responseMode: 'onReceived',
				path: 'webhooks',
			},
		],
		properties: [
			{
				displayName: 'Trigger On',
				name: 'topics',
				placeholder: 'Add Topic',
				options: [
					{
						name: 'Reservation',
						value: 'reservation/*',
						description: 'Subscribe to all reservation events',
					},
					{
						name: 'Booking',
						value: 'booking/*',
						description: 'Subscribe to all booking events',
					},
					{
						name: 'Folio',
						value: 'folio/*',
						description: 'Subscribe to all folio events',
					},
				],
				default: [],
				required: true,
				type: 'multiOptions',
			},
			{
				displayName: 'Property IDs',
				name: 'propertyIdsUI',
				placeholder: 'Add Property ID',
				type: 'fixedCollection',
				default: [],
				typeOptions: {
					multipleValues: true,
				},
				options: [
					{
						name: 'propertyIds',
						displayName: 'Property IDs',
						values: [
							{
								displayName: 'Property ID',
								name: 'propertyId',
								required: true,
								type: 'string',
								default: '',
							},
						],
					},
				],
			},
		],
	};

	webhookMethods = {
		default: {
			async checkExists(this: IHookFunctions): Promise<boolean> {
				const webhookData = this.getWorkflowStaticData('node');

				const webhookUrl = this.getNodeWebhookUrl('default') as string;

				const propertyIDsUI = this.getNodeParameter('propertyIdsUI') as IDataObject;
				const propertyIds = ParameterExtraction.getPropertyIds(
					propertyIDsUI.propertyIds as [{ propertyId: string }],
				);

				const events = this.getNodeParameter('topics') as string[];

				try {
					const { webhook } = await ApaleoWebhookApi.fetchWebhooks(
						this,
						webhookData.webhookId as string,
					);

					if (
						webhook.events.every((event) => events.includes(event)) &&
						webhook.propertyIds.every((propertyId) => propertyIds.includes(propertyId)) &&
						webhookUrl === webhook.endpointUrl
					) {
						webhookData.webhookId = webhook.id;
						return true;
					}

					// If it did not error then the webhook exists
					return false;
				} catch (err) {
					return false;
				}
			},
			async create(this: IHookFunctions): Promise<boolean> {
				const webhookData = this.getWorkflowStaticData('node');

				const webhookUrl = this.getNodeWebhookUrl('default') as string;

				const events = this.getNodeParameter('topics') as string[];

				const propertyIDsUI = this.getNodeParameter('propertyIdsUI') as IDataObject;

				const propertyIds = ParameterExtraction.getPropertyIds(
					propertyIDsUI.propertyIds as [{ propertyId: string }],
				);

				const responseData = await ApaleoWebhookApi.createWebHook(
					this,
					webhookUrl,
					events,
					propertyIds,
				);

				if (responseData === undefined || responseData.id === undefined) {
					// Required data is missing so was not successful
					return false;
				}

				webhookData.webhookId = responseData.id;

				return true;
			},
			async delete(this: IHookFunctions): Promise<boolean> {
				const webhookData = this.getWorkflowStaticData('node');

				if (webhookData.webhookId !== undefined) {
					try {
						await ApaleoWebhookApi.deleteWebhook(this, webhookData.webhookId as string);
					} catch (error) {
						return false;
					}

					// Remove from the static workflow data so that it is clear
					// that no webhooks are registred anymore
					delete webhookData.webhookId;
					delete webhookData.webhookEvents;
					delete webhookData.hookSecret;
				}

				return true;
			},
		},
	};

	async webhook(this: IWebhookFunctions): Promise<IWebhookResponseData> {
		// The data to return and so start the workflow with
		const bodyData = this.getBodyData();

		return {
			workflowData: [this.helpers.returnJsonArray(bodyData)],
		};
	}
}

GenericFunctions.ts:

import { IHookFunctions, jsonParse } from 'n8n-workflow';
import { OptionsWithUri } from 'request';

export namespace ParameterExtraction {
	export function getPropertyIds(complexArray: [{ propertyId: string }]) {
		const result: string[] = [];
		if (complexArray) {
			complexArray.forEach((el) => {
				if (el.propertyId && el.propertyId !== '') {
					result.push(el.propertyId);
				}
			});
		}

		return result;
	}
}

export namespace ApaleoWebhookApi {
	interface WebhookDetails {
		endpointUrl: string;
		events: string[];
		id: number;
		propertyIds: string[];
	}

	interface WebhookId {
		id: string;
	}

	interface Webhook {
		webhook: WebhookDetails;
	}

	const credentialsName = 'apaleoOAuth2Api';
	const baseURL = 'https://webhook.apaleo.com/v1';

	export const fetchWebhooks = async (ref: IHookFunctions, id: string): Promise<Webhook> => {
		const endpoint = `${baseURL}/subscriptions/${id}`;

		const options: OptionsWithUri = {
			method: 'GET',
			headers: {
				Accept: 'application/json',
			},
			uri: endpoint,
		};

		const webhooks = (await ref.helpers.requestWithAuthentication.call(
			ref,
			credentialsName,
			options,
		)) as string;

		return jsonParse(webhooks);
	};

	export const createWebHook = async (
		ref: IHookFunctions,
		endpointUrl: string,
		events: string[],
		propertyIds?: string[],
	): Promise<WebhookId> => {
		const body: any = {};

		if (propertyIds && propertyIds.length > 0) {
			body.propertyIds = propertyIds;
		}
		body.events = events;
		body.endpointUrl = endpointUrl;

		const endpoint = `${baseURL}/subscriptions`;

		const options: OptionsWithUri = {
			method: 'POST',
			headers: {
				Accept: 'application/json',
			},
			uri: endpoint,
			body: body,
		};

		const webhookId = await ref.helpers.requestWithAuthentication.call(
			ref,
			credentialsName,
			options,
		);

		return jsonParse(webhookId);
	};

	export const deleteWebhook = async (ref: IHookFunctions, webhookId: string) => {
		const endpoint = `${baseURL}/subscriptions/${webhookId}`;
		const body = {};

		const options: OptionsWithUri = {
			method: 'DELETE',
			headers: {
				Accept: 'application/json',
			},
			uri: endpoint,
			body,
		};

		return ref.helpers.requestWithAuthentication.call(ref, credentialsName, options);
	};
}

That is one way to do it, I was expecting a GitHub repo :smiley:

At first glance it looks ok, Are you making this as a community node or is it something you are creating for your own fork of n8n?

Hi @Jon, I am creating this for our own fork of n8n, not community

Hey @alexnemes,

That makes it a bit trickier for me to test but I can see if I can set aside some time to look over your code and see where it might be going wrong.

Thank you very much @Jon :slight_smile:

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.