Last week I had a business requirement about executing custom logic but only when the opportunity record goes from “in review” to “approved” status. In any other scenario the logic should not be executed.
So, I was thinking to accomplish this requirement by using a plugin with a pre and a post image, but then I though what If I can do this with a more “low code” approach, and that’s when I remembered webhooks.
Before going further, I would like to start by asking what do we know about webhooks?
According to the definition from the Microsoft’s documentation, it says that “CRM Webhooks is a lightweight HTTP pattern for connecting Web APIs and services with a publish/subscribe model”
In other words, this means that is a feature which enables us to call an external API or even our own service using HTTP request by linking it to some event that happens in Dataverse like create, update or even the deletion of a record.
Webhooks are present in Dynamics 365 CE world since version 9.0, end of 2017, but I think that they are not used that much, for instance I’ve used this feature a couple of times in real projects since 2019.
But despite of that, there are several posts in the community about how to call an Azure function or a cloud flow using webhooks, but as I said before, what if can do the same thing that I could do within a plugin but using the combination of a cloud flow with a webhook sending the pre and post images to the flow, in the end I wanted to know the pros and cons of using one option over the other.
To start I would like to mention there are two points that we must consider when we want to create a webhook:
- First, we must have a service (Some external API, or Azure function or in this case a cloud Flow which I will be using).
- Then we must create/register the webhook using the plugin registration tool, yes, our beloved tool that allow us to register plugins and workflow activities.
Now let’s go with the first point.
Create a Cloud Flow
If we want to call a cloud flow from a webhook, the flow’s trigger must be http request:
The next action could be a compose action, this is a temporary action for two reasons, one because a flow must have an additional action besides the trigger in order to save it, and also because we want to see what the flow is receiving from webhook.
For now, that is enough for the flow, we will continue later. Now with have to go for the second point that is create the webhook.
Create a WebHook
For this we have to use the plugin registration tool, it could be either using XRMToolbox or the official one from Microsoft. After we are connected to the environment, we can create a new webhook:
Next, we have to give a name to the webhook, and please allow me to give you a recommendation about this step, add a descriptive name that identify the webhook. And in the URL box we have to put the URL that is available in the flow trigger, so we have to go back to the flow and copy the URL from the trigger:
Before pasting it into Webhook, first paste it into a notepad file or whatever file editor you have because not all of the URL should be in the URL box, the parameters should not be in that part, and when I say parameters I’m referring to the query parameters, that is to say everything after ? character, in the following example the parameters are api-version, sp, sv and sig:
So, what we have to put in the URL box is what is specified up to the invoke word, and it is also important to mention that we have to replace the %2F characters with the / character in the parameter called sp, at the end that parameter must have this value:
Also, a good tool to do the last part could be: https://meyerweb.com/eric/tools/dencoder/ just copy and paste the flow URL and this toll will replace all %2F with /
After we’ve finished configuring the Webhook, and before adding the parameters, we have to select the “HttpQueryString” option in the Authentication box. In the end the configuration should be something similar to:
Click in Save button, and now we have to add the step, just like when we add a step for a plugin:
As I mentioned at the beginning the requirement, I had was to execute a logic only when the opportunity record goes from “in review” to “approved” status, so the step should be in the Update message and the entity should be the opportunity. It is important to mention that I want the old and new value of the status reason field, so we need to setup a PreImage and a PostImage, and to do that the step must be a PostOperation:
After register the step, the next action is to add the PreImage and PostImage:
First, we register the PreImage and be careful to register only the field or fields that you will need, do not send all the fields because it will make the flow take longer to finish, and in some scenarios, it causes that the Flows trigger twice:
Same for the PostImage, so in the end the webhook configuration should be something similar to:
Now we can make a test updating the status reason of the opportunity within the model driven app:
Click in save and then let’s see the flow execution log:
I recommend copying the outputs of the compose action and pasting them into a file editor such as visual studio code because first you need to understand the structure of the JSON that was sent to the cloud flow:
As we can see in the image above, the JSON is quite large, and we can see the structure for the PreImage and PostImage where the old and new values of the status reason field are located.
So, to get both values we have to create two expressions that I think are always the same when we want to get the old and the new value of a field, but just in case you can check them before, in my case these are the two expressions:
The first expression returns the old value and the second returns the new value of the field.
And if we put these expressions in variables within the flow it will look like this:
Then we have to add a condition action to validate the PreValue and PostValue, if the validation is correct, then we can apply the custom logic:
Then we can do our final test, so let’s go the opportunity and update again the status reason field:
Click in save and then let’s see the flow execution log:
There you go, we got the old value and the new value of a field using a more “low code” approach.
Scenarios in which it could be applied
I’m sure there are plenty of more scenarios where we can use this way combining webhooks with cloud flows.
Another example it could be when a record is deleted, we want to create a trace or log about the deleted record. If we use a normal cloud flow when the record is deleted, the only information we get is the ID, no more data is given to us. But if we use a webhook with a PreOperation step and a PreImage, then we can get all the information about the deleted record.
So, the Webhook configuration is the same but of course using another cloud flow, then add the PreOperation step and then the PreImage:
The PreOperation step:
And the PreImage, again be aware to add only the fields you’ll need:
And if we delete an opportunity, we’ll see the following flow execution log:
In this case I added an apply to each action iteration the following expression:
And inside the compose action these two expressions to see the name of the field and its value:
items(‘Apply_to_each’)?[‘key’] : items(‘Apply_to_each’)?[‘value’]
I did this because I wanted to get all fields that I sent in the PreImage.
Another example could be to run the logic only when the BPF goes from stage A to stage B, in which case the webhook step should be linked to the corresponding BFP table:
And again, in this case we must add a PreImage and a PostImage, the rest is the same.
Webhook + Flow or Plugin?
With the Power Platform, we got a lot of features that overlap with each other. I think all the functionality of CRM Webhooks combined with Flows can be done in plugin. So here are the pros and cons in my opinion:
- WebHooks combined with Flows is a low-code approach, I believe a functional user can do this, maybe they will struggle a bit with the expressions in the Cloud flow or maybe not, but other than that I think it’s a quick win to achieve a requirement like I mentioned using this method.
- For the plugin, we already know is a pro-code approach, and of course we have free space to customize as much as we want, In my opinion there is no rule that says, always use one option over the other, that will depend on the requirement/business case, for example when we are talking about applying custom logic to a large set of records, I prefer a faster way to do it using a plugin or even an Azure function.
- The cons of webhooks with Flows is the shortage of ways to handle errors. For example, if we create a webhook with a sync pre-op step, then we want to validate something within the cloud flow, say a value in some field, in case the value is not correct, we want to show an error to the user…. well…we can’t do that, the operation is effectively cancelled and a generic error message is displayed to the user. But that’s it. In my opinion that is not a good user experience to see a generic error and without knowing why the update operation is cancelled.
- And for the plugin’s cons we can say that needs coding so maybe it requires more time to construct the solution. And also, we can mention the maximum runtime of 2 minutes.
- As we have seen it is a low-code approach, we just need to create a cloud flow, and register the webhook and that’s it. Again, the hardest part is the expressions in the cloud flow to get the values.
- Using the plugin registration tool, we can attach the create, update or delete event to our cloud flow.
- It is important to use images, it can be pre or post images to get the correct values inside the cloud flow.
- If a cloud flow is used, please keep in mind that we have to create the expressions to get the values, triggerBody + question mark + square brackets with the name of the attribute + …
- Personally, I might recommend this approach if we have a scenario like the one I commented But if we must apply some logic immediately and if we must display a message to the user, then I do not recommend this approach. The same for applying logic to a large set of records.
- And when you use the plugin registration tool to create the webhook step, please don’t include all the fields, analyse first which fields you want to send to the flow, because otherwise the cloud flow might take too long to finish and maybe run twice. It happened to me when I used contact and account lookups. I avoided those two and used the customer field instead.
PD: Thank you Victor Sijtsma for helping me with this blogpost