Dynamics Ninja Logo


D365 Webhooks - Part 1

Cover Image for D365 Webhooks - Part 1
4 min read


Webhooks are present in Dynamics CE world since version 9.0, end of 2017, but I think that they are not used that much by the people in the community. Why should we start using them?

Webhooks is a lightweight HTTP pattern for connecting Web APIs and services with a publish/subscribe model.

Microsoft Docs

First of all, let's think about integrations that are done in Dynamics. Integrations are a pain point for most of the projects out there and it's even harder to make them the way we want with the limitations of the platform. Did you ever wanted to do something in your custom plugin, but the platform just forced you to go a different way that is not always an easy one.


  • We can’t use 3rd party libraries
  • 2-minute timeout
  • Port usage limitation (only 80 & 443 allowed)
  • We can’t use IP addresses, only named web addresses
  • Execution with partial trust
  • Resource heavy operations

Let's think about D365 webhooks as plugins that can be triggered in the Dynamics itself, but executed outside the platform. Because of that, we can overcome the limitations that are mentioned before. Sounds great?


There are 2 things we need to implement webhooks in Dynamics.

  1. Web service
  2. Webhook Configuration

We need to implement a service that will consume our webhook requests and the best way of doing that will be to put it in the Azure. Azure has multiple services that will do the job just fine, but Functions is the easiest way of doing that since it supports multiple programming languages.

Configuration part is done inside Plugin Registration Tool with few easy steps and it's very similar to registering a plugin step.

Web service

Azure Functions are just great for the start since we have almost everything that can be used for a quick start. We can use the portal inside Azure to code our functions, but I can tell that it's not the best idea because you will miss little things like IntelliSense or managing NuGet packages. Please use the Visual Studio for developing functions and save your time and nerves.

Azure Functions project can be easily created in Visual Studio by searching for functions in the templates.

After selecting the Azure Functions template you can start with the HTTP trigger template in one of the next steps which will provide you everything you need to make a simple function that can consume webhook request.

This will generate you a simple function that looks like:

    public static class Function1
        public static async Task Run([HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req, ILogger log)
            log.LogInformation("C# HTTP trigger function processed a request.");

            string name = req.Query["name"];

            string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
            dynamic data = JsonConvert.DeserializeObject(requestBody);
            name = name ?? data?.name;

            return name != null ?
                (ActionResult)new OkObjectResult($"Hello, {name}") :
                new BadRequestObjectResult("Please pass a name on the query string or in the request body");

The most important object that will be used in our webhooks scenario is the HttpRequest object. That object will hold everything you need from the Dynamics 365 event that triggered the webhook.

Let's do a slight modification to the function so we can log the Body content via built-in logger feature.

    public static class Function1
        public static async Task Run([HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req, ILogger log)
            string requestBody = await new StreamReader(req.Body).ReadToEndAsync();


            return (ActionResult)new OkObjectResult(requestBody);           

You can now publish the function to the Azure subscription by just pressing the right click Publish on the project in Visual Studio. After the successful publish you should head to the Azure portal and open the newly created resource.

The thing you need from here is URL to your function and it can be found in the section that is called like your function (Function1 in our case).

Press the Get function URL link and past the info somewhere for the later use.

URL should look something like:


Dynamics webhooks don't like special characters in functions keys so I advise you to renew the default key by going to Manage part until you don't have special characters (other than double = sign at the end) in your code.

Now we have everything to move to the next part.


The configuration is done with the newest version of the Plugin Registration Tool (not the one in XrmToolBox).

You can get the latest version in 2 places:

  1. NuGet package
  2. CDS.Tools

I suggest you to get it via CDS.Tools site because you can just click on the Get the SDK Tools! link and download the ZIP file.

Connect to your organization via PRT and select Register New Web Hook from the dropdown menu.

There you need to define Endpoint URL that is the first part (before the question mark) of URL you got on the Azure portal.

Then you need to select WebhookKey value in the Authentication dropdown and past the code value that you got on the portal.



After you entered the info hit Save button.

The final step is to register a new step to our webhook by right-clicking on the webhook and selecting Register new step. This one should be very familiar because it's not different from registering the plugin step.

Let's add sync post operation step on the Create action of Contact entity.

It's time to trigger our webhook by creating the contact entity in Dynamics. After you create a new contact in the Dynamics you should head to Functions portal in Azure and check the Monitor part.

If you set everything up you should see 1 execution in the logs which will contain logged JSON from the request body. This logs will be delayed for like 5 minutes so don't worry if you don't see them straight away.


This is just a brief overview of how to set the webhooks up and running, but we still didn't cover the in-depth structure of the request and how can you actually debug those things locally.

All those things will be covered in the next blog post that will be released soon.