diff --git a/packages/backend/src/apps/webhook/assets/favicon.svg b/packages/backend/src/apps/webhook/assets/favicon.svg
new file mode 100644
index 00000000..140ebd66
--- /dev/null
+++ b/packages/backend/src/apps/webhook/assets/favicon.svg
@@ -0,0 +1,8 @@
+
\ No newline at end of file
diff --git a/packages/backend/src/apps/webhook/auth/index.ts b/packages/backend/src/apps/webhook/auth/index.ts
new file mode 100644
index 00000000..ca3b207e
--- /dev/null
+++ b/packages/backend/src/apps/webhook/auth/index.ts
@@ -0,0 +1,5 @@
+import verifyWebhook from './verify-webhook';
+
+export default {
+ verifyWebhook,
+};
diff --git a/packages/backend/src/apps/webhook/auth/verify-webhook.ts b/packages/backend/src/apps/webhook/auth/verify-webhook.ts
new file mode 100644
index 00000000..22c2eaa9
--- /dev/null
+++ b/packages/backend/src/apps/webhook/auth/verify-webhook.ts
@@ -0,0 +1,7 @@
+import { IGlobalVariable } from '@automatisch/types';
+
+const verifyWebhook = async ($: IGlobalVariable) => {
+ return true;
+};
+
+export default verifyWebhook;
diff --git a/packages/backend/src/apps/webhook/index.d.ts b/packages/backend/src/apps/webhook/index.d.ts
new file mode 100644
index 00000000..e69de29b
diff --git a/packages/backend/src/apps/webhook/index.ts b/packages/backend/src/apps/webhook/index.ts
new file mode 100644
index 00000000..fbd480b8
--- /dev/null
+++ b/packages/backend/src/apps/webhook/index.ts
@@ -0,0 +1,16 @@
+import defineApp from '../../helpers/define-app';
+import auth from './auth';
+import triggers from './triggers';
+
+export default defineApp({
+ name: 'Webhook',
+ key: 'webhook',
+ iconUrl: '{BASE_URL}/apps/webhook/assets/favicon.svg',
+ authDocUrl: 'https://automatisch.io/docs/apps/webhook/connection',
+ supportsConnections: false,
+ baseUrl: '',
+ apiBaseUrl: '',
+ primaryColor: '0059F7',
+ auth,
+ triggers,
+});
diff --git a/packages/backend/src/apps/webhook/triggers/catch-raw-webhook/index.ts b/packages/backend/src/apps/webhook/triggers/catch-raw-webhook/index.ts
new file mode 100644
index 00000000..88c52f9f
--- /dev/null
+++ b/packages/backend/src/apps/webhook/triggers/catch-raw-webhook/index.ts
@@ -0,0 +1,20 @@
+import isEmpty from 'lodash/isEmpty';
+import defineTrigger from '../../../../helpers/define-trigger';
+
+export default defineTrigger({
+ name: 'Catch raw webhook',
+ key: 'catchRawWebhook',
+ type: 'webhook',
+ description: 'Triggers when the webhook receives a request.',
+
+ async testRun($) {
+ if (!isEmpty($.lastExecutionStep?.dataOut)) {
+ $.pushTriggerItem({
+ raw: $.lastExecutionStep.dataOut,
+ meta: {
+ internalId: '',
+ }
+ });
+ }
+ },
+});
diff --git a/packages/backend/src/apps/webhook/triggers/index.ts b/packages/backend/src/apps/webhook/triggers/index.ts
new file mode 100644
index 00000000..49159004
--- /dev/null
+++ b/packages/backend/src/apps/webhook/triggers/index.ts
@@ -0,0 +1,3 @@
+import catchRawWebhook from './catch-raw-webhook';
+
+export default [catchRawWebhook];
diff --git a/packages/backend/src/controllers/webhooks/handler.ts b/packages/backend/src/controllers/webhooks/handler.ts
index 72329adc..c8e51bb3 100644
--- a/packages/backend/src/controllers/webhooks/handler.ts
+++ b/packages/backend/src/controllers/webhooks/handler.ts
@@ -13,18 +13,19 @@ export default async (request: IRequest, response: Response) => {
.findById(request.params.flowId)
.throwIfNotFound();
- if (!flow.active) {
- return response.send(404);
- }
-
const triggerStep = await flow.getTriggerStep();
const triggerCommand = await triggerStep.getTriggerCommand();
+ const app = await triggerStep.getApp();
+ const isWebhookApp = app.key === 'webhook';
- if (triggerCommand.type !== 'webhook') {
- return response.send(404);
+ if (!flow.active && !isWebhookApp) {
+ return response.sendStatus(404);
+ }
+
+ if (triggerCommand.type !== 'webhook') {
+ return response.sendStatus(404);
}
- const app = await triggerStep.getApp();
if (app.auth.verifyWebhook) {
const $ = await globalVariable({
@@ -42,8 +43,20 @@ export default async (request: IRequest, response: Response) => {
}
}
+ // in case trigger type is 'webhook'
+ let payload = request.body;
+
+ // in case it's our built-in generic webhook trigger
+ if (isWebhookApp) {
+ payload = {
+ headers: request.headers,
+ body: request.body,
+ query: request.query,
+ }
+ }
+
const triggerItem: ITriggerItem = {
- raw: request.body,
+ raw: payload,
meta: {
internalId: await bcrypt.hash(request.rawBody, 1),
},
diff --git a/packages/backend/src/graphql/schema.graphql b/packages/backend/src/graphql/schema.graphql
index bd0c39b9..3f9714a3 100644
--- a/packages/backend/src/graphql/schema.graphql
+++ b/packages/backend/src/graphql/schema.graphql
@@ -342,6 +342,7 @@ type Step {
key: String
appKey: String
iconUrl: String
+ webhookUrl: String
type: StepEnumType
parameters: JSONObject
connection: Connection
diff --git a/packages/backend/src/helpers/global-variable.ts b/packages/backend/src/helpers/global-variable.ts
index de9a09e8..6834b081 100644
--- a/packages/backend/src/helpers/global-variable.ts
+++ b/packages/backend/src/helpers/global-variable.ts
@@ -77,6 +77,7 @@ const globalVariable = async (
id: execution?.id,
testRun,
},
+ lastExecutionStep: (await step?.getLastExecutionStep())?.toJSON(),
triggerOutput: {
data: [],
},
diff --git a/packages/backend/src/models/step.ts b/packages/backend/src/models/step.ts
index 719fbbf3..294671af 100644
--- a/packages/backend/src/models/step.ts
+++ b/packages/backend/src/models/step.ts
@@ -1,10 +1,11 @@
+import { URL } from 'node:url';
import { QueryContext, ModelOptions } from 'objection';
+import type { IJSONObject, IStep } from '@automatisch/types';
import Base from './base';
import App from './app';
import Flow from './flow';
import Connection from './connection';
import ExecutionStep from './execution-step';
-import type { IJSONObject, IStep } from '@automatisch/types';
import Telemetry from '../helpers/telemetry';
import appConfig from '../config/app';
@@ -46,7 +47,7 @@ class Step extends Base {
};
static get virtualAttributes() {
- return ['iconUrl'];
+ return ['iconUrl', 'webhookUrl'];
}
static relationMappings = () => ({
@@ -82,6 +83,13 @@ class Step extends Base {
return `${appConfig.baseUrl}/apps/${this.appKey}/assets/favicon.svg`;
}
+ get webhookUrl() {
+ if (this.appKey !== 'webhook') return null;
+
+ const url = new URL(`/webhooks/${this.flowId}`, appConfig.webhookUrl);
+ return url.toString();
+ }
+
async $afterInsert(queryContext: QueryContext) {
await super.$afterInsert(queryContext);
Telemetry.stepCreated(this);
@@ -106,6 +114,14 @@ class Step extends Base {
return await App.findOneByKey(this.appKey);
}
+ async getLastExecutionStep() {
+ const lastExecutionStep = await this.$relatedQuery('executionSteps')
+ .orderBy('created_at', 'desc')
+ .first();
+
+ return lastExecutionStep;
+ }
+
async getNextStep() {
const flow = await this.$relatedQuery('flow');
diff --git a/packages/backend/src/routes/webhooks.ts b/packages/backend/src/routes/webhooks.ts
index c26f4add..ca040cc0 100644
--- a/packages/backend/src/routes/webhooks.ts
+++ b/packages/backend/src/routes/webhooks.ts
@@ -3,6 +3,8 @@ import webhookHandler from '../controllers/webhooks/handler';
const router = Router();
+router.get('/:flowId', webhookHandler);
+router.put('/:flowId', webhookHandler);
router.post('/:flowId', webhookHandler);
export default router;
diff --git a/packages/types/index.d.ts b/packages/types/index.d.ts
index 0b2fb1f8..1386f962 100644
--- a/packages/types/index.d.ts
+++ b/packages/types/index.d.ts
@@ -54,6 +54,7 @@ export interface IStep {
key?: string;
appKey?: string;
iconUrl: string;
+ webhookUrl: string;
type: 'action' | 'trigger';
connectionId?: string;
status: string;
@@ -180,23 +181,16 @@ export interface IDynamicData {
export interface IAuth {
generateAuthUrl?($: IGlobalVariable): Promise;
- verifyCredentials($: IGlobalVariable): Promise;
- isStillVerified($: IGlobalVariable): Promise;
+ verifyCredentials?($: IGlobalVariable): Promise;
+ isStillVerified?($: IGlobalVariable): Promise;
refreshToken?($: IGlobalVariable): Promise;
verifyWebhook?($: IGlobalVariable): Promise;
isRefreshTokenRequested?: boolean;
- fields: IField[];
+ fields?: IField[];
authenticationSteps?: IAuthenticationStep[];
reconnectionSteps?: IAuthenticationStep[];
}
-export interface IService {
- authenticationClient?: IAuthentication;
- triggers?: any;
- actions?: any;
- data?: any;
-}
-
export interface ITriggerOutput {
data: ITriggerItem[];
error?: IJSONObject;
@@ -300,6 +294,7 @@ export type IGlobalVariable = {
id: string;
testRun: boolean;
};
+ lastExecutionStep?: IExecutionStep;
webhookUrl?: string;
triggerOutput?: ITriggerOutput;
actionOutput?: IActionOutput;