Skip to main content
Version: 1.0.6

Webhooks

In this tutorial, we will show how to register webhooks to get realtime information about events in integrated services. For the demonstration, we will again use the GitHub application we created in the Basic Application tutorial. If you haven't read this tutorial, we recommend you study it first.

Principle

The principle of webhooks is simple. We tell the integrated service on its API where to send the requested event. For this to work, we need to have the authorization of the integrated service sorted out first.

We handle the registration and unregistration of webhooks in Orchesty directly within the application.

Webhooks application

First, we need to change type of Application by override getApplicationType method and add the application interface IWebhookApplication. Next step is create a RequestDto object in the new getWebhookSubscribeRequestDto method to subscribe the webhook and a processWebhookSubscribeResponse method to handle the registration.

// ...
import ApplicationTypeEnum from '@orchesty/nodejs-sdk/dist/lib/Application/Base/ApplicationTypeEnum';
import { IWebhookApplication } from '@orchesty/nodejs-sdk/dist/lib/Application/Base/IWebhookApplication';
import ProcessDto from '@orchesty/nodejs-sdk/dist/lib/Utils/ProcessDto';
import WebhookSubscription from '@orchesty/nodejs-sdk/dist/lib/Application/Model/Webhook/WebhookSubscription';
import ResponseDto from '@orchesty/nodejs-sdk/dist/lib/Transport/Curl/ResponseDto';

export default class GitHubApplication extends ABasicApplication implements IWebhookApplication {

// ...

public getApplicationType(): ApplicationTypeEnum {
return ApplicationTypeEnum.WEBHOOK;
}

public getWebhookSubscribeRequestDto(
applicationInstall: ApplicationInstall,
subscription: WebhookSubscription,
url: string,
): RequestDto {
const request = new ProcessDto();
const { owner, record } = subscription.getParameters();
return this.getRequestDto(
request,
applicationInstall,
HttpMethods.POST,
`/repos/${owner}/${record}/hooks`,
{
config: {
url,
// eslint-disable-next-line @typescript-eslint/naming-convention
content_type: 'json',
},
name: 'web',
events: [subscription.getName()],
},
);
}

public processWebhookSubscribeResponse(
dto: ResponseDto,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
applicationInstall: ApplicationInstall,
): string {
if (dto.getResponseCode() !== 201) {
throw new Error((dto.getJsonBody() as { message: string }).message);
}

return (dto.getJsonBody() as { id: string }).id;
}

// ...

}

Unsubscribe

We do the same for unsubscribe webhooks.

// ...
import Webhook from '@orchesty/nodejs-sdk/dist/lib/Application/Database/Webhook';

export default class GitHubApplication extends ABasicApplication implements IWebhookApplication {

// ...

public getWebhookUnsubscribeRequestDto(applicationInstall: ApplicationInstall, webhook: Webhook): RequestDto {
const webhookSubscription = this.getWebhookSubscriptions().find(
(item) => item.getName() === webhook.getName(),
);
if (!webhookSubscription) {
throw new Error(`Webhook with name [${webhook.getName()}] has not been found.`);
}

const { record, owner } = webhookSubscription.getParameters();

const request = new ProcessDto();
return this.getRequestDto(
request,
applicationInstall,
HttpMethods.DELETE,
`/repos/${owner}/${record}/hooks/${webhook.getWebhookId()}`,
);
}

public processWebhookUnsubscribeResponse(dto: ResponseDto): boolean {
return dto.getResponseCode() === 204;
}


// ...

}

Webhook Subscriptions

Finally, the GetWebhookSubscriptions method defines the webhooks that our application will allow to register.


export default class GitHubApplication extends ABasicApplication implements IWebhookApplication {

// ...

public getWebhookSubscriptions(): WebhookSubscription[] {
return [
new WebhookSubscription('issues', 'Webhook', '', { record: 'record', owner: 'owner' }),
new WebhookSubscription('pull-request', 'Webhook', '', { record: 'record', owner: 'owner' }),
];
}

// ...

}

Full application code

import ApplicationTypeEnum from '@orchesty/nodejs-sdk/dist/lib/Application/Base/ApplicationTypeEnum';make 
import CoreFormsEnum from '@orchesty/nodejs-sdk/dist/lib/Application/Base/CoreFormsEnum';
import { IWebhookApplication } from '@orchesty/nodejs-sdk/dist/lib/Application/Base/IWebhookApplication';
import { ApplicationInstall } from '@orchesty/nodejs-sdk/dist/lib/Application/Database/ApplicationInstall';
import Webhook from '@orchesty/nodejs-sdk/dist/lib/Application/Database/Webhook';
import Field from '@orchesty/nodejs-sdk/dist/lib/Application/Model/Form/Field';
import FieldType from '@orchesty/nodejs-sdk/dist/lib/Application/Model/Form/FieldType';
import Form from '@orchesty/nodejs-sdk/dist/lib/Application/Model/Form/Form';
import FormStack from '@orchesty/nodejs-sdk/dist/lib/Application/Model/Form/FormStack';
import WebhookSubscription from '@orchesty/nodejs-sdk/dist/lib/Application/Model/Webhook/WebhookSubscription';
import { ABasicApplication, TOKEN } from '@orchesty/nodejs-sdk/dist/lib/Authorization/Type/Basic/ABasicApplication';
import RequestDto from '@orchesty/nodejs-sdk/dist/lib/Transport/Curl/RequestDto';
import ResponseDto from '@orchesty/nodejs-sdk/dist/lib/Transport/Curl/ResponseDto';
import { HttpMethods } from '@orchesty/nodejs-sdk/dist/lib/Transport/HttpMethods';
import AProcessDto from '@orchesty/nodejs-sdk/dist/lib/Utils/AProcessDto';
import { CommonHeaders, JSON_TYPE } from '@orchesty/nodejs-sdk/dist/lib/Utils/Headers';
import ProcessDto from '@orchesty/nodejs-sdk/dist/lib/Utils/ProcessDto';

export const NAME = 'git-hub';

export default class GitHubApplication extends ABasicApplication implements IWebhookApplication {

public getApplicationType(): ApplicationTypeEnum {
return ApplicationTypeEnum.WEBHOOK;
}

public getName(): string {
return NAME;
}

public getPublicName(): string {
return 'GitHub';
}

public getDescription(): string {
return 'Service that helps developers store and manage their code, as well as track and control changes to their code';
}

public getFormStack(): FormStack {
const form = new Form(CoreFormsEnum.AUTHORIZATION_FORM, 'Authorization settings')
.addField(new Field(FieldType.TEXT, TOKEN, ' Token', undefined, true));

return new FormStack().addForm(form);
}

public isAuthorized(applicationInstall: ApplicationInstall): boolean {
const authorizationForm = applicationInstall.getSettings()[CoreFormsEnum.AUTHORIZATION_FORM];
return authorizationForm?.[TOKEN];
}

public getRequestDto(
dto: AProcessDto,
applicationInstall: ApplicationInstall,
method: HttpMethods,
uri?: string,
data?: unknown,
): RequestDto {
const request = new RequestDto(`https://api.github.com${uri}`, method, dto);
if (!this.isAuthorized(applicationInstall)) {
throw new Error(`Application [${this.getPublicName()}] is not authorized!`);
}
const form = applicationInstall.getSettings()[CoreFormsEnum.AUTHORIZATION_FORM] ?? {};
request.setHeaders({
[CommonHeaders.CONTENT_TYPE]: JSON_TYPE,
[CommonHeaders.ACCEPT]: 'application/vnd.github+json',
[CommonHeaders.AUTHORIZATION]: `Bearer ${form[TOKEN]}`,
});

if (data) {
request.setJsonBody(data);
}

return request;
}

public getWebhookSubscribeRequestDto(
applicationInstall: ApplicationInstall,
subscription: WebhookSubscription,
url: string,
): RequestDto {
const request = new ProcessDto();
const { owner, record } = subscription.getParameters();
return this.getRequestDto(
request,
applicationInstall,
HttpMethods.POST,
`/repos/${owner}/${record}/hooks`,
{
config: {
url,
content_type: 'json',
},
name: 'web',
events: [subscription.getName()],
},
);
}

public getWebhookSubscriptions(): WebhookSubscription[] {
return [
new WebhookSubscription('issues', 'Webhook', '', { record: 'record', owner: 'owner' }),
new WebhookSubscription('pull-request', 'Webhook', '', { record: 'record', owner: 'owner' }),
];
}

public getWebhookUnsubscribeRequestDto(applicationInstall: ApplicationInstall, webhook: Webhook): RequestDto {
const webhookSubscription = this.getWebhookSubscriptions().find(
(item) => item.getName() === webhook.getName(),
);
if (!webhookSubscription) {
throw new Error(`Webhook with name [${webhook.getName()}] has not been found.`);
}

const { record, owner } = webhookSubscription.getParameters();

const request = new ProcessDto();
return this.getRequestDto(
request,
applicationInstall,
HttpMethods.DELETE,
`/repos/${owner}/${record}/hooks/${webhook.getWebhookId()}`,
);
}

public processWebhookSubscribeResponse(
dto: ResponseDto,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
applicationInstall: ApplicationInstall,
): string {
if (dto.getResponseCode() !== 201) {
throw new Error((dto.getJsonBody() as { message: string }).message);
}

return (dto.getJsonBody() as { id: string }).id;
}

public processWebhookUnsubscribeResponse(dto: ResponseDto): boolean {
return dto.getResponseCode() === 204;
}

}

Application registration

Since we have only extended a previously developed application, we do not need to register it. If you don't already have a HubSpot application registered in the container, see the Basic application tutorial.

Creating a topology

Webhooks topology