Webhooks are a popular approach for enabling real-time notifications and updates between applications. They provide an efficient way to communicate with external systems and automate workflows. In this blog post, we'll explore how to develop a webhook system using NestJS, to enable event-based integrations with external systems.

Setting up a NestJS Application


First, let's start by installing the NestJS CLI  globally (if you do not have it already) to initialize and set up NestJS applications:

npm i -g @nestjs/cli
nest new webhook-app


This command will create a simple web API that exposes a single endpoint at the root address (localhost:3000/). For our example, we will create an endpoint to simulate order creation and notify an external shipping application to pick up the product. We will work on app.service.ts to send the request to our root application:

// app.service.ts
import { Injectable } from '@nestjs/common'; 

@Injectable() 
export class AppService { 
  createOrder(data) { 
    // we omit the implementation of order creation 
    return data; 
  } 
}

Now, let's import the WebhookService into our AppController and call the notifyShippingApplication method when a new order is created:

// app.controller.ts
import { Body, Controller, Post } from '@nestjs/common'; 
import { AppService } from './app.service'; 
import { WebhookService } from './webhook.service';

@Controller() 
export class AppController { 
  constructor(
    private readonly appService: AppService,
    private readonly webhookService: WebhookService,
  ) {} 

  @Post('/order') 
  async createOrder(@Body() data) { 
    const createdOrder = this.appService.createOrder(data); 

    // Notify the shipping application using the webhook
    await this.webhookService.notifyShippingApplication(createdOrder);

    return createdOrder; 
  } 
}

Testing the Webhook System

Finally, let's test that the webhook is called correctly when creating a new order. You can use a tool like Postman to make the request to our "localhost:3000/order" endpoint:

POST /a13b5452-5f37-485b-be82-92b84a1c30ab HTTP/1.1
Host: webhook.site
Content-Type: application/json
Content-Length: 85
 
{
    "productId": 1,
    "amount": 1,
    "deliveryAddress": "Fake Street 123"
}

Now, open webhook.site in a browser tab and verify that a request was received with the data that we sent to our webhook. If the webhook.site were the shipping app, it would already have been notified to pick up our product at the address we sent.

Using an Interceptor to automate the webhook notification

To implement this, first create a new interceptor called WebhookInterceptor:

// webhook.interceptor.ts
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
import { WebhookService } from './webhook.service';

@Injectable()
export class WebhookInterceptor implements NestInterceptor {
  constructor(private readonly webhookService: WebhookService) {}

  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    return next.handle().pipe(
      tap(async (createdOrder) => {
        await this.webhookService.notifyShippingApplication(createdOrder);
      }),
    );
  }
}

Now, you need to bind the WebhookInterceptor to the createOrder method in the AppController. You can do this by using the @UseInterceptors() decorator:

// app.controller.ts
import { Body, Controller, Post, UseInterceptors } from '@nestjs/common'; 
import { AppService } from './app.service'; 
import { WebhookInterceptor } from './webhook.interceptor';

@Controller() 
export class AppController { 
  constructor(private readonly appService: AppService) {} 

  @Post('/order') 
  @UseInterceptors(WebhookInterceptor)
  createOrder(@Body() data) { 
    const createdOrder = this.appService.createOrder(data); 
    return createdOrder; 
  } 
}

And that's it!!! With this implementation, the WebhookInterceptor will automatically call the notifyShippingApplication method after the createOrder method is executed, without the need to explicitly call it in the createOrder method itself.

This approach allows you to keep the webhook notification logic separate from the main route handler, making your code more modular and maintainable.
you can find the full source code on Github.