Trigger flow when a Marketing or Subscription List is updated
There is a bigger story around the Dynamics Marketing disarray between marketing permissions, consents and subscriptions that could make your mind melt. But that is for a later post. The main thing is that we need to be able to check the marketing permissions and consent are correctly synchronised with the customers subscriptions whenever they change, and vice versa!
This post will focus on how to trigger a flow when a Marketing or Subscription list is updated, understand what kind of update just happened, then get the GUIDs you need to do your next steps on checking consent and permissions. I used some inspiration and brain power along the way courtesy of Tip #1362: Trigger Power Automate on Associate / Disassociate and George Doubinski (which are kind of the same thing but lets not go into that right now).
WARNING: I use weird scary words like ‘Webhook’ and ‘HTTP Request’. You will need to install the almighty ‘Plugin Registration Tool’ but I promise to hold your hand on the way through so no one gets left behind.
2) Install and connect to the ‘Plugin Registration Tool’
Download the latest SDK Tools Zip FIle -> https://xrmtoolscode.azureedge.net/sdk.zip. DO NOT SKIP THIS BIT -> After you download, right-click properties and unblock the file before you unzip it locally. Seriously, I checked, you really do need to do this or nothing works and you have to go cry in the corner for a while. Unzip the file and put it somewhere useful, or go rogue - leave it to get lost in the abyss of your Downloads folder so you can kick yourself later.
Create a new connection, make sure you select ‘Display list of available organisations’ and ‘Show Advanced’. Log in with your Dynamics/Microsoft 365 credentials and select the environment you are working in. Welcome to the inner cavern of the Plugin Registration Tool, don’t worry I feel as uncomfortable as you do right now.
2) Regsiter a ‘Webhook’ and some ‘Steps’
No seriously, I’m not even joking. Don’t forget to breathe. We need to breakdown the HTTP POST URL from step 1) into Webhook size chunks, prepare this first. Replace ‘&’ or ‘?’ with a new line and replace ‘%2F’ with ‘/’ and tadah!
Before
https://prod-07.australiasoutheast.logic.azure.com:443/workflows/abc123456789z/triggers/manual/paths/invoke?api-version=2016-06-01&sp=%2Ftriggers%2Fmanual%2Frun&sv=1.0&sig=abcdefghi
After
Endpoint URL:
https://prod-07.australiasoutheast.logic.azure.com:443/workflows/abc123456789z/triggers/manual/paths/invoke
Keys:
api-version=2016-06-01
sp=/triggers/manual/run
sv=1.0
sig=abcdefghi
Register -> Register New Web Hook -> Use the values you just created above, to fill in the Endpoint URL and Keys. Change the Authentication to HttpQueryString - if you don’t do this now, it doesn’t work later, even if you change it, trust me I checked (twice) -> Save. Webhook - Registered!
Next up ‘Steps’, we need to add the following steps, these are the ‘Messages’
AddMember
Disassociate
RemoveMember
Right click on the Webhook you just created -> Register new Step -> Set Message e.g. ‘AddMember’ -> Set Execution Mode ‘Asynchronous -> don’t touch anything else and pretend you know what you are doing -> click Register New Step button
Repeat these stapes for all the three items above until your Webhook looks a little something like this —>
3) Check out your body
The body of the flow of course. Make sure your flow is turned ON and go add/remove some contacts from some marketing lists. Remember there are lots of ways to do this, the end result is the same (either the contact is added or removed) but the inputs that come from the Webhook trigger are completely different. Plus these steps are going to fire on any relevant AddMember/RemoveMember/Disassociate step - not just for the contact to marketing list relationship. Ugh. The good news is, we can totally deal with that in flow - yay!
4) Initialise your variables
I don’t really like using variables too much and prefer my good friend ‘Compose’ but things got a bit confusing without them. We will initiate the following string variables which are all values we will be getting out of the Webhook trigger ‘InputParameters’
ListId
ContactId
Relationship
MemberIds
RelatedEntities
Target
And if you want to be super snazzy (anal) put them all in parallel 🤓
5) Do you have a Parent Context?
Sometimes the ‘InputParameters’ that we need have a parent context, sometimes they don’t, but if they do - it’s very useful! Add a ‘Condition’ action to check if the ParentContext is blank or not, if it’s blank then we skip, if not then we go get dig for some hidden InputParameters!
triggerBody()?['ParentContext']
Within the ParentContext you will see there are multiple objects, each with a different ‘key’ - the key name determines which variable we will set. In this sample you can see ‘Target’, ‘Relationship’ and ‘Related Entities’ - these will help us to determine if the activity is related to the thing we want - Marketing Lists and Contacts. And if so, we need those lovely GUID’s to then act accordingly later on . So now ‘all you need to do’ is set the variables to these values…
Add an ‘Apply to Each’ control. Then inside there, add a ‘Switch’ control. Set the inputs as per below. We do this so we can go through every object, find out what the key is called, and grab the useful piece of info we want from there.
Select an output from previous step: triggerBody()?['ParentContext/InputParameters'] On: items('Apply_to_each_2')?['key']
Now we need to add multiple ‘Cases’ to the switch for each possible key, then tell it what variable we want to set and which attribute from the object we want to use.
6) Then the Input Parameters
We need to repeat a very similar set of logic (with a few additions') to cover the situations when there is no ParentContext, but also to provide additional information that cannot be gained from the ParentContext even when it exists. Outisde of the ‘ParentContextNull condiiton, add another ‘Apply to Each’ control. Then inside there, add a ‘Switch’ control. Set the inputs as per below.
Select an output from previous step: triggerBody()?['InputParameters'] On: items('Apply_to_each')?['key']
Again we will add multiple ‘Cases’ to the switch for each possible key, then tell it what variable we want to set and which attribute from the object we want to use. Notice that in this sate of cases, we sometimes set more than one variable per case.
6) Check yourself before you wreck yourself
This is a good checkpoint to add a ‘Compose’ step, add all your variables to it (or copy-pasta from below) and run a few tests to check that they are getting populated with the right values and the flow is running smoothly.
Message: @{triggerBody()?['MessageName']} ListId: @{variables('ListId')} ContactId: @{variables('ContactID')} Relationship: @{variables('Relationship')} MemberIds: @{variables('MemberIds')} RelatedEntities: @{variables('RelatedEntities')} Target: @{variables('Target')}
7) Was a Marketing or Subscription List updated?
Finally, we need to check if actually we even care about this transaction - is this related to a Contact being added or removed from a Marketing /Subscription List?
Add a Condition control and using the variables you have populated from above we are going to check if the ListID has been populated (not null)
If it is false, cancel the flow (Terminate action with status cancel)
If it is true, using a ‘Get Row’ action, table name - marketing list, row ID - the ListId you set earlier, select columns - msdyncrm_issubscription,listname (optional but tidy 😊)
This is the bit I wanted to know all along, and it feeds nicely into my next post on Marketing Consent and Preferences because I only want to update them when subscriptions are changed.
Add a Condition control and check that the list you just ‘got’ above is in fact a ‘subscription’ list, i.e. outputs('GetMarketingList')?['body/msdyncrm_issubscription'] is true
If it is false, cancel the flow (Terminate action with status cancel)
triggerBody()?['MessageName'] if(contains(variables('MemberIds'),variables('ContactID')),variables('MemberIds'),concat(variables('ContactID'),variables('MemberIds')))