All you need to know about Business Central Webhooks

Webhooks is a great feature introduced in Business Central to notify other applications about data changes occurring in entities. Webhooks are a way for applications to communicate between them automatically. When a record is created / updated / deleted in Business Central, a notification is sent to the subscriber automatically.



Comparison between Publisher-Subscriber Events and Webhooks

Webhooks' design pattern is very similar to Publisher-Subscriber events in AL, but it is over HTTP. The design pattern (PubSub) for Publisher-Subscriber and Webhooks is same. The below comparison may help you to understand the differences better.


Publisher-Subscriber events

  • Publisher-Subscriber events work within Business Central Extensions.

  • Subscriber functions are executed synchronously.

  • Subscriber function can cause performance issues because it is executed within the transaction.

Webhooks

  • Webhooks work across applications. Publisher is Business Central and the subscriber can be any other application.

  • Webhooks work asynchronously.

  • There will not be any performance impact to Business Central.

Use cases

Extend the Business Central functionality

  • Generating Airway bill on Sales Shipment in Warehouse application.

  • Sending E-Mail, SMS, WhatsApp notifications to customer on Sales Order release by an external application.

Synchronize the Business Central data

  • Synchronize Customer, Vendor masters with CRM application

  • Synchronize Inventory masters with Warehouse application.

How to create Webhook

As of now, all APIs in Business Central support Webhooks, with the following exceptions:

  1. API page with temporary / system table as source

  2. API page with a composite key

  3. API type query

Working with Webhooks

Subscriber for Business Central Webhook is always an external application. Typically, its a web application developed in .Net / Node.js, or it can be in any other framework / language.


The following series of code are written in Node.JS, and ExpressJS to illustrate Webhook operations. Business Central on premise is used just to avoid the complexity of registering Client Application in Azure portal, generating OAuth2 Authorization Token, etc.


Setup

The following JavaScript code listens to https port 3000 and assigns some constants which will be used in other functions.

const fs = require("fs");
const httpntlm = require("httpntlm");
const express = require("express");
const https = require("https");

const companyId = "74e35bd0-2590-eb11-bb66-000d3abcddd1";
const baseUrl = "http://localhost:7048/BC180";

// replace with actual values
const defaults = {
  username: "USER-NAME",
  password: "PASSWORD",
  workstation: "",
  domain: "DOMAIN-NAME",
};

const app = express();
const port = 3000;

const key = fs.readFileSync("./ssl/key.pem");
const cert = fs.readFileSync("./ssl/cert.pem");
const server = https.createServer({ key: key, cert: cert }, app);

app.use(express.json());
app.use(
  express.urlencoded({
    extended: true,
  })
);

server.listen(port, () => {
  console.log(`Example app listening at https://localhost:${port}`);
}); 

Register a Webhook Subscription

The following code registers webhook subscription for the Customer entity. If a customer is created, updated or deleted a notification is send to the subscriber.

app.get("/register-webhook", function (req, res) {
  const body = {
    notificationUrl: `https://localhost:${port}/customer-notification`,
    resource: `/api/v2.0/companies(${companyId})/customers`,
    clientState: "state123",
  };

  const options = {
    ...defaults,
    headers: {
      "Content-Type": "application/json",
    },
    url: `${baseUrl}/api/v2.0/subscriptions`,
    body: JSON.stringify(body),
  };

  httpntlm.post(options, function (err, response) {
    if (err) {
      return err;
    }

    res.setHeader("content-type", "application/json");
    res.send(response.body);
  });
});

Receive Notifications

The following code receives notifications from Business Central when a Customer entity is updated. Also, it executes at the time of registering / renewing the subscription to validate "notificationUrl".

app.post("/customer-notification", function (req, res) {
  // response to validation requests
  if (req.query.validationToken) {
    res.send(req.query.validationToken);
    return;
  }

  console.log("Customer entity updates:");
  console.log(req.body);
  res.send("");
});

Note: When creating a subscription and renewing a subscription, the client has to return the "validationToken" in the body with response code 200.


Renew the Webhook Subscription

By default, the Webhook Subscription will be expired after 3 days if it is not renewed. This setting can be changed in "CustomSettings.config" file. The following code renews the Webhook Subscription for the Customer entity.


 
app.post("/renew-subscription", function (req, res) {
  const subscriptionId = req.body.subscriptionId;
  const eTag = req.body.eTag;

  const body = {
    notificationUrl: `https://localhost:${port}/customer-notification`,
    resource: `/api/v2.0/companies(${companyId})/customers`,
    clientState: "state123",
  };

  const options = {
    ...defaults,
    url: `${baseUrl}/api/v2.0/subscriptions('${subscriptionId}')`,
    headers: {
      "Content-Type": "application/json",
      "If-Match": eTag,
    },
    body: JSON.stringify(body),
  };

  httpntlm.patch(options, function (err, response) {
    if (err) {
      return err;
    }

    res.setHeader("content-type", "application/json");
    res.send(response.body);
  });
});

Unsubscribe the Webhook Subscription

It is always better to unsubscribe when the notifications are no longer needed, otherwise the system has to try again and again until it expires. The following code deletes the Webhook Subscription.

app.post("/delete-subscription", function (req, res) {
  const subscriptionId = req.body.subscriptionId;
  const eTag = req.body.eTag;

  const options = {
    ...defaults,
    url: `${baseUrl}/api/v2.0/subscriptions('${subscriptionId}')`,
    headers: {
      "If-Match": eTag,
    },
  };

  httpntlm.delete(options, function (err, response) {
    if (err) {
      return err;
    }

    res.setHeader("content-type", "application/json");
    if (response.statusCode == 204) {
      res.send("subscription deleted");
    } else {
      res.send(response.body);
    }
  });
});

Get Subscriptions

The following code gets the active Webhook Subscriptions in Business Central.

app.get("/get-subscriptions", function (req, res) {
  const options = {
    ...defaults,
    url: `${baseUrl}/api/v2.0/subscriptions`,
  };

  httpntlm.get(options, function (err, response) {
    if (err) {
      return err;
    }

    res.setHeader("content-type", "application/json");
    res.send(response.body);
  });
});

Webhook Limitations

Only create, update and delete events are supported to send notifications to the subscribers. At the present, It is not possible to create a custom notification for an event like sales order approved / rejected, order shipped, delivered etc.

Instead, Firebase can be used to publish custom notifications, but that's for another post.


An anonymous URL is required to subscribe Webhook therefore, Business Central cannot be a subscriber. We always need an external application to get notifications from other applications into Business Central.

Power-Automate can be one of the options to overcome this limitation.


Conclusion

Webhooks are very helpful to send notifications to external applications asynchronously without compromising performance.


This is the area where compromised designs are seen in most of the implementations. Often, Push & Pull / Polling techniques are used to synchronize data with external applications, but both techniques can cause performance issues. Pull / Polling technique brings unnecessary load on the system by requesting data repeatedly at set intervals. Push technique can cause delay in completing the transaction (which causes locking errors).


Push is always better than Pull but in an asynchronous way (using job queues or background sessions). Webhooks does the same thing without any need of writing a single line of code.


Happy Coding!!!


You can find complete source code at GitHub.


#MSDyn365 #MSDyn365BC #BusinessCentral #DynamicsNAV #NodeJS #Webhooks