diff --git a/packages/nodes-base/credentials/AcuitySchedulingApi.credentials.ts b/packages/nodes-base/credentials/AcuitySchedulingApi.credentials.ts
new file mode 100644
index 000000000..5ee2344ef
--- /dev/null
+++ b/packages/nodes-base/credentials/AcuitySchedulingApi.credentials.ts
@@ -0,0 +1,23 @@
+import {
+ ICredentialType,
+ NodePropertyTypes,
+} from 'n8n-workflow';
+
+export class AcuitySchedulingApi implements ICredentialType {
+ name = 'acuitySchedulingApi';
+ displayName = 'Acuity Scheduling API';
+ properties = [
+ {
+ displayName: 'User ID',
+ name: 'userId',
+ type: 'string' as NodePropertyTypes,
+ default: '',
+ },
+ {
+ displayName: 'API Key',
+ name: 'apiKey',
+ type: 'string' as NodePropertyTypes,
+ default: '',
+ },
+ ];
+}
diff --git a/packages/nodes-base/nodes/AcuityScheduling/AcuitySchedulingTrigger.node.ts b/packages/nodes-base/nodes/AcuityScheduling/AcuitySchedulingTrigger.node.ts
new file mode 100644
index 000000000..2166b5cc8
--- /dev/null
+++ b/packages/nodes-base/nodes/AcuityScheduling/AcuitySchedulingTrigger.node.ts
@@ -0,0 +1,163 @@
+import {
+ IHookFunctions,
+ IWebhookFunctions,
+} from 'n8n-core';
+
+import {
+ IDataObject,
+ INodeTypeDescription,
+ INodeType,
+ IWebhookResponseData,
+} from 'n8n-workflow';
+
+import {
+ acuitySchedulingApiRequest,
+} from './GenericFunctions';
+
+export class AcuitySchedulingTrigger implements INodeType {
+ description: INodeTypeDescription = {
+ displayName: 'Acuity Scheduling Trigger',
+ name: 'acuitySchedulingTrigger',
+ icon: 'file:acuityScheduling.png',
+ group: ['trigger'],
+ version: 1,
+ description: 'Handle Acuity Scheduling events via webhooks',
+ defaults: {
+ name: 'Acuity Scheduling Trigger',
+ color: '#000000',
+ },
+ inputs: [],
+ outputs: ['main'],
+ credentials: [
+ {
+ name: 'acuitySchedulingApi',
+ required: true,
+ }
+ ],
+ webhooks: [
+ {
+ name: 'default',
+ httpMethod: 'POST',
+ responseMode: 'onReceived',
+ path: 'webhook',
+ },
+ ],
+ properties: [
+ {
+ displayName: 'Event',
+ name: 'event',
+ type: 'options',
+ required: true,
+ default: '',
+ options: [
+ {
+ name: 'appointment.scheduled',
+ value: 'appointment.scheduled',
+ description: 'is called once when an appointment is initially booked',
+ },
+ {
+ name: 'appointment.rescheduled',
+ value: 'appointment.rescheduled',
+ description: 'is called when the appointment is rescheduled to a new time',
+ },
+ {
+ name: 'appointment.canceled',
+ value: 'appointment.canceled',
+ description: 'is called whenever an appointment is canceled',
+ },
+ {
+ name: 'appointment.changed',
+ value: 'appointment.changed',
+ description: 'is called when the appointment is changed in any way',
+ },
+ {
+ name: 'order.completed',
+ value: 'order.completed',
+ description: 'is called when an order is completed',
+ },
+ ],
+ },
+ {
+ displayName: 'Resolve Data',
+ name: 'resolveData',
+ type: 'boolean',
+ default: true,
+ description: 'By default does the webhook-data only contain the ID of the object.
If this option gets activated it will resolve the data automatically.',
+ },
+ ],
+ };
+ // @ts-ignore
+ webhookMethods = {
+ default: {
+ async checkExists(this: IHookFunctions): Promise {
+ const webhookData = this.getWorkflowStaticData('node');
+ if (webhookData.webhookId === undefined) {
+ return false;
+ }
+ const endpoint = '/webhooks';
+ const webhooks = await acuitySchedulingApiRequest.call(this, 'GET', endpoint);
+ if (Array.isArray(webhooks)) {
+ for (const webhook of webhooks) {
+ if (webhook.id === webhookData.webhookId) {
+ return true;
+ }
+ }
+ }
+ return false;
+ },
+ async create(this: IHookFunctions): Promise {
+ const webhookUrl = this.getNodeWebhookUrl('default');
+ const webhookData = this.getWorkflowStaticData('node');
+ const event = this.getNodeParameter('event') as string;
+ const endpoint = '/webhooks';
+ const body: IDataObject = {
+ target: webhookUrl,
+ event,
+ };
+ const { id } = await acuitySchedulingApiRequest.call(this, 'POST', endpoint, body);
+ webhookData.webhookId = id;
+ return true;
+ },
+ async delete(this: IHookFunctions): Promise {
+ const webhookData = this.getWorkflowStaticData('node');
+ const endpoint = `/webhooks/${webhookData.webhookId}`;
+ try {
+ await acuitySchedulingApiRequest.call(this, 'DELETE', endpoint);
+ } catch(error) {
+ return false;
+ }
+ delete webhookData.webhookId;
+ return true;
+ },
+ },
+ };
+
+ async webhook(this: IWebhookFunctions): Promise {
+ const req = this.getRequestObject();
+
+ const resolveData = this.getNodeParameter('resolveData', false) as boolean;
+
+ if (resolveData === false) {
+ // Return the data as it got received
+ return {
+ workflowData: [
+ this.helpers.returnJsonArray(req.body),
+ ],
+ };
+ }
+
+ // Resolve the data by requesting the information via API
+ const event = this.getNodeParameter('event', false) as string;
+ const eventType = event.split('.').shift();
+ const endpoint = `/${eventType}s/${req.body.id}`;
+ const responseData = await acuitySchedulingApiRequest.call(this, 'GET', endpoint, {});
+
+ return {
+ workflowData: [
+ this.helpers.returnJsonArray(responseData),
+ ],
+ };
+
+
+ }
+}
diff --git a/packages/nodes-base/nodes/AcuityScheduling/GenericFunctions.ts b/packages/nodes-base/nodes/AcuityScheduling/GenericFunctions.ts
new file mode 100644
index 000000000..9a2d0fc59
--- /dev/null
+++ b/packages/nodes-base/nodes/AcuityScheduling/GenericFunctions.ts
@@ -0,0 +1,42 @@
+import { OptionsWithUri } from 'request';
+import {
+ IExecuteFunctions,
+ IExecuteSingleFunctions,
+ IHookFunctions,
+ ILoadOptionsFunctions,
+ IWebhookFunctions,
+} from 'n8n-core';
+import { IDataObject } from 'n8n-workflow';
+
+export async function acuitySchedulingApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions | IWebhookFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any
+ const credentials = this.getCredentials('acuitySchedulingApi');
+ if (credentials === undefined) {
+ throw new Error('No credentials got returned!');
+ }
+
+ const options: OptionsWithUri = {
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ auth: {
+ user: credentials.userId as string,
+ password: credentials.apiKey as string,
+ },
+ method,
+ qs,
+ body,
+ uri: uri ||`https://acuityscheduling.com/api/v1${resource}`,
+ json: true
+ };
+ try {
+ return await this.helpers.request!(options);
+ } catch (error) {
+
+ let errorMessage = error.message;
+ if (error.response.body && error.response.body.message) {
+ errorMessage = `[${error.response.body.status_code}] ${error.response.body.message}`;
+ }
+
+ throw new Error('Acuity Scheduling Error: ' + errorMessage);
+ }
+}
diff --git a/packages/nodes-base/nodes/AcuityScheduling/acuityScheduling.png b/packages/nodes-base/nodes/AcuityScheduling/acuityScheduling.png
new file mode 100644
index 000000000..df087b60e
Binary files /dev/null and b/packages/nodes-base/nodes/AcuityScheduling/acuityScheduling.png differ
diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json
index 829d20caf..e72da6de6 100644
--- a/packages/nodes-base/package.json
+++ b/packages/nodes-base/package.json
@@ -27,6 +27,7 @@
"n8n": {
"credentials": [
"dist/credentials/ActiveCampaignApi.credentials.js",
+ "dist/credentials/AcuitySchedulingApi.credentials.js",
"dist/credentials/AirtableApi.credentials.js",
"dist/credentials/Amqp.credentials.js",
"dist/credentials/AsanaApi.credentials.js",
@@ -86,6 +87,7 @@
"dist/nodes/ActiveCampaign/ActiveCampaign.node.js",
"dist/nodes/ActiveCampaign/ActiveCampaignTrigger.node.js",
"dist/nodes/Airtable/Airtable.node.js",
+ "dist/nodes/AcuityScheduling/AcuitySchedulingTrigger.node.js",
"dist/nodes/Amqp/Amqp.node.js",
"dist/nodes/Amqp/AmqpTrigger.node.js",
"dist/nodes/Asana/Asana.node.js",