Dynamics Ninja Logo

Blog.

D365 Webhooks – Part 2

Cover Image for D365 Webhooks – Part 2
·
5 min read

Introduction

Last time we did a configuration of a simple D365 webhook with Azure Functions. This time we will check how to implement sync/async webhooks and upgrade it to parse the data that is passed as the context to the webhook function. If you are not familiar with configuring webhooks you should definitely start with Part 1 of the blog series before reading this one.

Async/Sync Webhooks

You can register 2 types of webhooks in Dynamics and you can guess it's asynchronous & synchronous ones. It's pretty straight forward to know the basic difference between those two, but there is always something in the background that is good to know when you implement them.

Asynchronous Webhooks

The name tells us that those kinds of webhooks are initiated in an asynchronous manner. It makes the most suitable for long-running jobs that will take some time and we don't want to wait for the execution to end. That way we don't get the response right away, but we should be able to check the execution results somehow. It's done the same way you would do it for every single async operation in Dynamics and that is in System Jobs page.

System jobs page can be reached by following the path below.

Settings > System > System Jobs

Example of one failed execution is shown in the picture below.

System Jobs does not provide much information about the error, but at least we can find the info when something failed to execute. We need to implement error logging on the webhook request handler side as the Message box suggests.

Synchronous Webhooks

Synchronous webhooks are just the opposite of the async ones. Sync ones allow us to get the response in the real-time when the event rises. This benefit also leads to the few restrictions that we must consider while implementing sync webhooks.

The first restriction is that every request that ends outside the 2xx range HTTP code will result as a failed process. This will result in rollback on the whole transaction that initiated the webhook and we will get the let's say not pleasant popup dialog that is out of our control. We can't pass the data to the dialog and it's showing some generic error message that is not helping us to determine the real cause of the error and even if you click Show Detail and download the log you will not get something useful to trace the issue.

The second restriction is a timeout. Timeout is here even lower than the timeout of the workflows (2 minutes). Sync webhook timeout is set to 60 seconds. In the end timeout results also with the transaction rollback.

Request content

When the event is raised in Dynamics the event data is sent by a webhook in a form of the HTTP POST request.

We can divide that data into 3 types:

  • Query String
  • Header
  • Request Body

Query String

The query string parameter holds the least information of all the content posted in the request. You can get 2 different values here:

  • WebhookKey
  • HttpQueryString

Both values are set in the configuration of the webhook via Plugin Registration Tool which can be used to ensure that request comes from the trusted source.

Header

The header contains a little bit more information than the query string. List of all information can be found in the table below.

Most of the parameters are self-explanatory, but it's good to know what's the important thing here.

The first one I want to highlight here is x-ms-dynamics-organization parameter that will give us information about the URL of the tenant that is sending the request which is pretty useful for multi-tenant implementations where you can have single Webhook to handle the requests from multiple instances.

The second one I want to talk about here is x-ms-dynamics-msg-size-exceeded. This one is important because it can cause troubles if you totally ignore it. The point is that it's set only if the HTTP payload exceeds the 256KB in size. If the request is greater than the threshold request will not contain information about ParentContext, InputParameters, PreEntityImages & PostEntityImages which can be very useful in some scenarios, so you should be careful while handling big payloads.

Request Body

The most important part of the HTTP request is its body. The body contains all the information about the actual event that happened in the Dynamics. It contains a JSON object that has many nodes filled with useful information.

You can find the example of one JSON that is returned when a contact record is created on the link below. An example is stored in the external side just because it's too big to show it in the post.

JSON Example

Assuming that you are familiar with Dynamics plugin development, if you look carefully in the JSON you can see some familiar parameters there. All parameters are shown in the picture above and you can see that it's pretty much the same as the object that you used many times before in plugins. We are talking of RemoteExecutionContext class that is used in every plugin code.

Boxes marked in green are the most used parameters and will contain the most useful data, so you can focus on those when trying to find the right info you need.

Now we need to deserialize this big JSON the RemoteExecutionContext. We will do it like we use to deserialize JSONs in our plugin code with the snippet below.

public static RemoteExecutionContext GetRemoteExecutionContextFromJson(string jsonString){
    using (var ms = new MemoryStream(Encoding.Unicode.GetBytes(jsonString))){
        var serializer = new DataContractJsonSerializer(typeof(RemoteExecutionContext));
        var context = (RemoteExecutionContext)serializer.ReadObject(ms);
        return context;
    }
}

Function from the snippet accepts the JSON string and as an output gives the RemoteExecutionContext object that can be used in our C# development.

Azure Function Upgrade

Now when we have the code to deserialize JSON to the known object we are ready to upgrade our Azure function to use it.

First, we need to install the NuGet package (Microsoft.CrmSdk.CoreAssemblies) that will allow us to use RemoteExecutionContext in our code.

Let's update the code in the function that will log which user has created the contact record.

[FunctionName("LogUser")]
public static async Task<HttpResponseMessage> Run([HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)]HttpRequestMessage req, TraceWriter log)
{
    string requestBody = await req.Content.ReadAsStringAsync();

    var remoteContext = GetRemoteExecutionContextFromJson(requestBody);

    log.Info($"User ({remoteContext.InitiatingUserId}) has created the contact.");

    return req.CreateResponse(HttpStatusCode.OK, remoteContext.InitiatingUserId);
}

public static RemoteExecutionContext GetRemoteExecutionContextFromJson(string jsonString)
{
    using (var ms = new MemoryStream(Encoding.Unicode.GetBytes(jsonString)))
    {
        var serializer = new DataContractJsonSerializer(typeof(RemoteExecutionContext));
        var context = (RemoteExecutionContext)serializer.ReadObject(ms);
        return context;
    }
}

After deploying the function just like we did it in the first part of the blog we should get the log in the Monitor section of the function just like it's shown on the picture below.

There you go! Now you can use the data coming from Dynamics event in your Azure Function like you are used to in the plugin development.

Conclusion

It seems like we have everything ready to make more serious stuff, but we are still missing the debugging part. At the moment we can just trace our errors and variables in the log to find the bug or recreate the requests coming from the instance and sending them to local Azure Function. Sounds like a boring and long-running process?

In this part, we made our way to the final part of the blog series that will show us how to debug the webhook request that comes from Dynamics online instance directly to local Azure Function so we can finally start making more complex scenarios.