Webhooks with Dynamics 365 Business Central

When integrating external applications with Dynamics 365 Business Central, one of the classical way is to use APIs (standard or custom APIs) and then call the relative endpoints from the external application.

As said in the past, exposing entities by using standard or custom API pages is the recommended way for doing integrations with Dynamics 365 Business Central. By using APIs, the schema of your integration is the following:

Webhooks_Schema_01.jpg

Here, an external application calls a Dynamics 365 Business Central API endpoint by using simple HTTP methods and then it receives a JSON response from the ERP.

One of the problem that you can have when integrating external systems is that sometimes the external application needs to react to changes that occours on ERP entities. Imagine for example to have an external application that needs to handle an external processing (for example shipments labels printing) for every sales order that comes from Dynamics 365 Business Central. Instead of pulling the ERP for retrieving orders and their modifications on some scheduled timing (for example every N minutes), could be useful if the ERP notifies directly the external application every time an entity changes.

This is exactly the concept of Webhooks: Webhooks is the way to get notified if an entity changes in Dynamics 365 Business Central:

Webhooks_Schema_02.jpg

In Dynamics 365 Business Central, every entity exposed as API supports webhooks natively.

To start working with Webhooks, you need to follow a request/response workflow in order to establish the communication:

  1. You need to register a webhook subscription with the entities you want to be subscribed to by providing a notification url (endpoint where Dynamics 365 Business Central will send the notifications for your subscribed entities).
  2. You need to return a validation token to Dynamics 365 Business Central from your notification url (handshake).
  3. When the subscription is established, you can start receiving notifications and you can then renew or remove the subscriptions as needed.

To show how you can work with Webhooks in Dynamics 365 Business Central, imagine that we have an external system that needs to be notified on every changes that occours on a Sales Order record in Dynamics 365 Business Central.

I’ve deployed on my Azure subscription an Azure Function that receives a JSON body (notification from Dynamics 365 Business Central with the details of the modified entity) and then performs some work as needed by our business scenario. When deployed, the Azure Function has a public URL (endpoint) and this is the notification url that we’ll use when establishing the webhook subscription to the Sales Order entity in our ERP.

NOTE 1: in our calls here we’re using Basic authentication, but you can use also OAuth2 and in this case the TENANTID parameter is not needed.

NOTE 2: In the samples provided I’m connected to my main production environment (called Production) but you can change this name accordingly with the name of the Dynamics 365 Business Central environment you want to use.

Checking subscriptions

As a first step, we call the Dynamics 365 Business Central Subscriptions API in order to check if there are active webhooks subscriptions on our tenant. For this, we need to send a GET http request to the following endpoint:

GET https://api.businesscentral.dynamics.com/v2.0/TENANTID/production/api/v1.0/subscriptions

and the response from the tenant is as follows:

D365BCWebhooks_01.png

The response is a JSON array of the active webhook subscriptions and in this case it’s empty (no subscriptions established).

Registering a webhook subscription

To register a webhook subscription, we need to send a POST request to the following endpoint, with Content-Type = application/json:

POST https://api.businesscentral.dynamics.com/v2.0/TENANTID/production/api/v1.0/subscriptions
and by passing the following JSON object in the body:
{

  "notificationUrl": "https://d365bcwebhooks.azurewebsites.net/api/D365BCListener?code=MYFUNCTIONSECRETKEY",

  "resource": "https://api.businesscentral.dynamics.com/v2.0/TENANTID/production/api/v1.0/companies(COMPANYID)/salesOrders",

  "clientState": "SomeSharedSecretForTheNotificationUrl"

}
Here:
  • notificationUrl is the url of my published Azure Function that will receive notifications from Dynamics 365 Business Central. My Azure Function is protected with AuthorizationLevel = Function so the url must provide the access key defined in the Azure Portal for accessing the function.
  • resource is the API address of the Dynamics 365 Business Central resource that we want to subscribe (in this case I want to subscribe the salesOrders API). It’s important to remember that the subscription is per-company, so you need to provide the company id.
  • clientState is optional and can be included as an “opaque key”, enabling the subscriber to verify notifications.

When the POST request is issued to the subscription API, Dynamics 365 Business Central sends a request to the notificationUrl endpoint by passing a validationToken parameter in the query string.

The subscriber (in my case my Azure Function) retrieves this token from the query string and then it sends back to the caller (Dynamics 365 Business Central) a response with a body that contains the validationToken and by setting http status code = 200 (success).

When Dynamics 365 Business Central receives the response, it checks for the validationToken and if all is ok the subscription is established.

This is the returned response that shows that the webhook subscription is activated (with a proper subscriptionId and expirationDateTime):
D365BCWebhooks_02
If I monitor what happens on my Azure Function, I can see that a request is received (see the spike to the Server requests section of the dashboard):
D365BCWebhooks_03

and if I check the detail of the caller (Azure Application Insights is an extremely useful tool for that), I can see that the caller is an application that comes from North Holland (datacenter location of my Dynamics 365 Business Central tenant):

D365BCWebhooks_04.png

P.S. this is a nice way for knowing the exact location of the datacenter region where your Dynamics 365 Business Central tenant runs.

Receiving notifications

What happens now that my webhook subscription is activated? Dynamics 365 Business Central starts sending notifications to my endpoint every time something happens on the subscribed entities (pushing).

Here we’ve subscribed for receiving notifications about Sales Orders. If I modify a Sales Order in Dynamics 365 Business Central, a notification is sent to my Azure Function (notificationUrl parameter).

This is what you can see from the Azure Portal after modifying something on a Sales Order record:

D365BCWebhooks_06.png

As you can see from the Azure Function monitor (Server requests diagram), a new HTTP request is arrived to my endpoint:

D365BCWebhooks_07.png

and if you inspect the incoming request details, you can see that the caller is always a cloud application that comes from the same region as above mentioned (my Dynamics 365 Business Central tenant).

To show the incoming body request (notification details sent from Dynamics 365 Business Central to my Azure Function) I have logged the incoming request body in the Azure Function log. As you can see in the picture below, it contains a JSON with the modified entity (resource url) and its related state change (created, updated, deleted):

D365BCWebhooks_08.png

Wow… Dynamics 365 Business Central has sent you a notification! 🙂

Please always remember that:

  • a notification sent to the subscriber (notificationUrl endpoint) can contain multiple notifications from different entities (subscriptions)
  • Dynamics 365 Business Central will not send a notification immediately when an entity changes (normally it occours some minutes). This is by design in order to permit to send a single notification even though the entity might have changed several times within a few seconds.

Renewing a webhook subscription

Webhooks subscriptions expires in 3 days (default value). They can be renewed by sending a PATCH request to the subscriptions API (as in the following URL) with Content-Type = application/json:

PATCH https://api.businesscentral.dynamics.com/v2.0/TENANTID/production/api/v1.0/subscriptions('SUBSCRIPTIONID')

This is not well documented by Microsoft actually and it’s a source of problems, but remember that in order to successfully renew a webhook subscription, you need to retrieve the @odata.etag value from the response received when you have established the subscription (see above) and then pass this value as a If-Match block in the PATCH request header.

Without this, you will receive an error that says “Could not validate the client concurrency token required by the service. Please provide a valid token in the client request.”.

Remember also that the @odata.etag value must be passed unescaped (so change \” to “) otherwise you will receive an error saying that “Request data is invalid” (HTTP 400 Bad Request error).

Renewing a subscription also updates the @odata.etag value, so be aware of this (you need to save the new value after the renewal).

NOTE for the on-premise version: the duration for a subscription is 3 days as default also for the on-premise version. This value is specified in the CustomSettings.config file under the ApiSubscriptionExpiration entry. In this file there is also a maximum number of subscriptions admitted specified in the ApiSubscriptionMaxNumberOfSubscriptions entry.

Delete a webhook subscription

To delete a webhook subscription, you can send a DELETE http request to the following endpoint:

DELETE https://api.businesscentral.dynamics.com/v2.0/TENANTID/production/api/v1.0/subscriptions('SUBSCRIPTIONID')

Also this is actually not well documented by Microsoft, but to successfully delete a webhook subscription you need to do exactly like the renewing process previously described (If-Match header with the @odata.etag value).

List of webhooks supported entities

To have a list of all the webhooks supported entities, you can send a GET request to the webhookSupportedResources endpoint (please be aware of the endpoint url):

GET https://api.businesscentral.dynamics.com/v2.0/TENANTID/production/api/microsoft/runtime/beta/companies(COMPANYID)/webhookSupportedResources
This is the response for my tenant:

D365BCWebhooks_09.png

Here you can see all your standard and custom entities exposed as APIs.

As you can see, Webhooks are extremely powerful and permits you to have a “push-based solution”, where is the ERP that calls you when something happens.

From the external application, in order to have a complete workflow always running by code (no human interaction) you need to:

  • Establish the subscription to an entity and save the @odata.etag, subscriptionId and expirationDateTime values.
  • Handle the notifications accordingly to your business needs. On a notification you have the changeType parameter (what happened on your subscribed entity) and the resource endpoint (that you can use to retrieve the entity details).
  • When you’re near to the expirationDateTime value, you need to renew the subscription (by passing the subscriptionId) and save the new @odata.etag and expirationDateTime values.

 

 

13 Comments

  1. i don’t suppose you have a github where you stored this test azure function, do you?! loved this article, still trying to wrap my head around how the azure function portion works though. is it…multiple functions within that azure function, or one function with multiple parameters or…?? i’ve never used azure functions so when i created a function and then it makes me create my first function i got lost on what the proper procedure is from there.

    Like

  2. Really good post, I’m interested in practical use of this – but I can’t figure out what happens if either BC or 3rd party System is down for a few milliseconds when this communication happens – BC is not 100 % online, nor is the 3rd Party System in a cloud world. So what kind of error handling is needed compared to a traditional “Queue it and Process it” approach. If one wanted to use this, can it only be used for “less” important notifications – or could it be used for critical areas too?

    Like

    1. You can secure the webhooks flow as per your needs. Common way for having 99.99% reliability is to use queues like Azure Queue or Azure Service Bus for storing the message that arrives from D365BC and process it later.

      Like

  3. Hi Stefano,

    Very useful indeed. It seems the only way for BC online to notify another service if e.g. a record is created and this record is not exposed by default. Currently i m trying to get this done in order to expose changes in cust ledger entries, but i cant fully get how to build the azure function. I ld appreciate if you can share an example/ guide of the function if possible. Thanks!

    Like

  4. Hi Stefano,
    I’m trying to test subscrition to webhook on my test environment, but even passing the validationToken back in the response I always get
    “Service has not provided a valid validation token. CorrelationId: 1216ee7f-5e5a-443c-9338-b6e4180ed06a.”

    My Request is as follow:
    POST [MYSERVER]:7048/bc/api/v1.0/subscriptions
    {
    “notificationUrl”: “[MYNOTIFICATIONSERVER]/subscribe”,
    “resource”: “[]MYSERVER]/bc/api/v1.0/companies(1aaef56e-78af-4eb4-b7e7-a479774a679f)/customers”
    }

    My notification server respond with this body:

    {
    validationToken:213f871d-e87f-4562-b505-97cccfc8f22d
    }

    Can you please help me

    Like

      1. Hi René,
        I tried to use webhook.site to track my webhooks issued from BC16 but I still got a badrequest with error “Service has not provided a valid validation token”
        How did you format the JSon / clientState to hve authentication to webhook.site …?
        Thanks a lot.

        Like

  5. Thanks for the blog. It’s really helpful. I’m trying to use webhook with custom API. I can see my custom API in the list of supported subscription but it always failed when I try to register webhook the error message is “No HTTP resource was found that matches the request URI” .
    Any idea ? Thanks.

    Like

  6. Hi Stefano, extremely interesting and useful article for another time. Thanks for the blog. Have you ever experienced the webhook for multiple consecutive runs (4) to have an output of collection with the same resource? (i.e. exactly the same lastModifiedDateTime filter on the table)

    Like

      1. yes, actually four times. Our case is accessing a custom api for customer ledger entries, so we have add a lastmodifieddate field in the cus. ledger entries table and in the api, in order the collection filter to work in the webhook. We are posting 100 invoices. We are using the webhook in an Azure LogicApp to integrate data to D365 CRM.
        For some reason in 4 runs the same collection is returned as output from the webhook:
        “resource”: “xxxiLimited/xxxxyyyy/v1.0/companies(74c2176-5232-4740-assdf-27e59875c1)/CustomerLedgerEntries?$filter=lastModifiedDateTime%20gt%202020-10-20T14:55:13.5Z”,
        “changeType”: “collection”,

        And as hard as i have tried to find an explanation (i.e. when or how is fired the webhook from the BC) in the documents, i cannot find something. Are you aware for any technical docs about it?
        Best regards

        Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.