D365 Marketing Weekly
Have you seen the D365 Marketing Weekly newsletter yet?
A weekly issue covering features, functionality and news on the topic of Marketing, specifically covering Dynamics 365 Marketing and other interesting tools and tips for anyone interested in the subject.
Subscribe Here
*** NOTE: ALL INFORMATION IS ACCURATE AT DATE OF PUBLISHING ***

If you use Google Analytics, you should be familiar with channels. As defined by Google, channels are rule-based definitions of your website’s traffic sources that let you monitor the performance of all of the channels sending traffic to your website. Someone might come to your website, but not via a link including UTM Parameters which can be used to uncover more information about how they found your site. Understanding the method or ‘channel’ used is important to know where your marketing budget has actually been spent on something helping generate new Leads. In this post, I will provide a way in which to gather that information to pass through via a submitted Realtime Marketing form and using browser session storage so you can start tracking your Lead Channels.

For this approach, you will need to use and understand the basics of the following:

  • Adding new fields to the Lead table in D365
  • Creating Realtime Marketing Forms
  • Adding Realtime Marketing Forms to your website
  • Google Tag Manager

If you’ve got an understanding of all of the above (or have access to someone that does), read on. This approach should be used for all of your RTM forms ideally, but is flexible enough to be used just for one or two forms if required. First, it would be worth reading up on the default channel group used in Google Analytics. This is where the list of channels comes from. There are 18 default channels in total but my solution accounts for only 11 of them based on the niche options or unique channels include in the list that I am unable to truly test. You should be able to use my script as a good starting point, then follow the logic in the definitions and add in more rules should you need to. When viewing certain reports in GA4, it should look something like this when reviewing channel data.

Click to view in detail

D365 Configuration

Go ahead and add a new global Choice field called Lead Channel. This should then be used when adding new Lead Channel fields on the Lead table so the values are consistent. Make a note of the value of each label as these will be needed to update the final Lead Channel Capture script. Then use that global field to add a new Lead Channel field on the Lead table. Create a field to capture the Landing Page of the visitor which will get the first URL they land on within your website. The Referrer URL will capture the link of where they came from (unless coming direct from an Email or typing in your website address directly). These are both needed to look at the various rules to determine which Lead Channel they fall in to.

If you don’t have them already, add in the additional fields listed below to make sure you have a full well rounded solution. That way if someone landing on your site DID have UTM Parameters in the link they clicked on, you will still get that information too.

  • Lead Channel – choice field including the channels to track
  • Landing Page – single text
  • Referrer URL – single text
  • UTM Source – single text
  • UTM Medium – single text
  • UTM Campaign – single text
  • UTM Content – single text

These are the channels I’ve added to my environment that are all included in the lead channel script provided later on.

Click to view in detail

Realtime Marketing Form

For each marketing form that you wish to use to capture the lead channel information, make sure you add all of the new fields that were added to the Lead table, and make sure they are hidden.

Click to view in detail

When adding the new form to your website, you access the script and copy from the form in Dynamics 365. It will look something like this below. Moving forwards, only add the form elements to your web page (blue part) and do not include the script (purple part). This will be added to Google Tag Manager to make sure the form loads at the right time and also captures the correct values from any UTM Parameters or Referral information.

Click to view in detail

Directly below the blue part on your web page, add the following. This will make sure that when someone visits the page with the form, the value of rtmFormExists will be pushed through as an event to Google Tag Manager and be an indicator that several scripts need to be fired to run.

<script>
    window.dataLayer = window.dataLayer || [];
    window.dataLayer.push({
        'event': 'rtmFormExists'
    });
</script>

Google Tag Manager Setup

This is the longest part to get set up but is where all the magic happens. Google Tag Manager will need to have several components set up for this to work. This blog assumes knowledge of GTM exists already. As with most things in GTM, setting up the components can be a little chicken and egg where something needs to be set up that references something else first. If set up in the following order, everything should be linked together correctly. Although you can use different naming logic, you may then need to edit other things later on to take that in to account.

Variables Required

The following variables need to be created in GTM:

Determine First Page View Information

Variable Details: This variable is used to return a value from the sessionStorage that will be set if this is the first page view in the current session for the person on the website. This makes sure that later on, the landing page and referral page urls only get set once and on the first page the visitor lands on.

Variable Name: RTM – First Page View

Variable Type: Custom JavaScript

Variable Script:

function() {
      return sessionStorage.getItem("firstHit");
  }

Triggers Required

The following triggers need to be created in GTM:

First Page View

Trigger Details: Used in conjunction with the Variable and Tag of a similar name. This triggers when the First Page View variable contains the value of True (set using the first page view tag below).

Trigger Name: RTM – First Page View

Trigger Type – Page View > Window Loaded

Trigger Fires On – Some Window Loaded Events

Trigger Event – 1st drop down, First page View. 2nd drop down, equals. 3rd drop down, true.

Marketing Form Exists On Page

Trigger Details: Used to determine if the event was fired from the script added directly below the marketing form details on the website.

Trigger Name: RTM – Form Exists

Trigger Type: Custom Event

Trigger Event Name: rtmFormExists

Trigger Fires On: All Custom Events

Tags Required

The following tags need to be created in GTM:

First Page View

Tag Details: This tag sets in to the sessionStorage if this is a first page view or a subsequent visit in the session of the person on your website.

Tag Name: First Page View

Tag Type: Custom HTML

Tag Script:

<script>
// check if item has been set on a previous hit
if  (window.sessionStorage)  {
    if  (sessionStorage.getItem("firstHit"))  {
        sessionStorage.setItem('firstHit',  'false');
    }  else  {
// otherwise set this hit as first page view
        sessionStorage.setItem('firstHit',  'true');
    }
}
</script>

Tag Firing Trigger: All Page Views

Set Current Session Information

Tag Details: This checks to see if the current referrer and landing are already stored in the sessionStorage, and if not, it stores them if present.

Tag Name: Session Info Current

Tag Type: Custom HTML

Tag Script:

<script>
  // Check if referrer is already stored in sessionStorage
  var referrer = sessionStorage.getItem("session_referrer");
  var landing = sessionStorage.getItem("session_landing");
  // If it's not stored, get it from document.referrer and store it
  if (!referrer) {
    referrer = document.referrer;
    sessionStorage.setItem("session_referrer", referrer);
  }
  // If it's not stored, get it from window.location.href and store it
  if (!landing) {
    landing = window.location.href;
    sessionStorage.setItem("session_landing", landing);
  }
</script>

Tag Firing Trigger: First Page View

D365 Realtime Marketing Form Loader Script

Tag Details: This tag contains the part of the script that you removed from the original Realtime Marketing form script provided by Microsoft. It will load after the Lead Channel Capture script so that the values get loaded first from the Referral and Landing Page, and are populated into the values on the form. This script is used to actually load the form.

Tag Name: RTM – D365 RTM Form Loader

Tag Type: Custom HTML

Tag Script:

<script src='https://cxppusa1formui01cdnsa01-endpoint.azureedge.net/global/FormLoader/FormLoader.bundle.js'></script>

Tag Firing Trigger: None

Lead Channel Capture Script

Tag Details: This is the main script that is running to extract all of the correct information from the sessionStorage to get the original and/or current referral, utm and lead channel information and add it to the hidden fields on your RTM form so they are added to the Lead created.

Tag Name: RTM – Lead Channel Capture

Tag Type: Custom HTML

Tag Extra Info: Select Support document.write

Tag Advanced Settings: Tag firing options should be Once per event

Tag Sequencing: Fire a tag before RTM Lead Channel Capture fires then select Session Info Current. This makes sure the Session Info is populated prior to the Lead Channel Capture script running so it can access the URL information needed.

Tag Firing Trigger – RTM – Form Exists

Tag Script:

<script>
// Define the findInputByLabelText function in the global scope
function findInputByLabelText(labelText) {
  var labels = document.querySelectorAll('label[title="' + labelText + '"]');
  for (var i = 0; i < labels.length; i++) {
    var label = labels[i];
    var inputId = label.getAttribute("for");
    if (inputId) {
      var inputElement = document.getElementById(inputId);
      if (inputElement) {
        return inputElement;
      }
    }
  }
  return null;
}

function processRulesAndPopulateFields(sessionLanding) {
  // Define recognized social networks, social media sites, and search engines
  var recognizedSocialNetworks = ["facebook", "twitter", "instagram", "youtube", "linkedin","linktree"];
  var socialMediaSites = ["facebook.com", "twitter.com", "instagram.com", "linkedin.com","lnkd.in","t.co"];
  var searchEngines = ["google", "bing", "yahoo","duckduckgo"];
  var excludeWords = ["email", "adwords", "ppc", "paidsearch", "paidsocial", "cpc"];

// Retrieve values from sessionStorage and decode them
var sessionLandingEncoded = sessionStorage.getItem("session_landing");
var sessionLanding = decodeURIComponent(sessionLandingEncoded);


  // Extracting information from the sessionLanding value
  var sessionParams = new URL(sessionLanding);
  var sessionUtmSource = sessionParams.searchParams.get('utm_source');
  var sessionUtmMedium = sessionParams.searchParams.get('utm_medium');
  var sessionUtmCampaign = sessionParams.searchParams.get('utm_campaign');
  var sessionUtmContent = sessionParams.searchParams.get('utm_content');

  // Get the referrer values from sessionStorage
  var sessionReferrer = sessionStorage.getItem('session_referrer');
 // Check if referrer values are not empty and are valid URLs
  var sessionDomain = getDomainFromURL(sessionReferrer);

function getDomainFromURL(urlString) {
  try {
    // Add a default protocol if missing
    var urlWithProtocol = urlString.startsWith('http') ? urlString : 'https://' + urlString;

    // Return the hostname if the URL is valid
    return new URL(urlWithProtocol).hostname;
  } catch (error) {
    // Log the error if the URL is invalid
    console.error('Error parsing URL:', urlString, error.message);
    return 'N/A';
  }
}

// Find the right fields
  var referrerUrlField = findInputByLabelText("Referrer URL");
  var sessionLandingField = findInputByLabelText("Last Landing Page");
  var utmSourceField = findInputByLabelText("UTM Source");
  var utmCampaignField = findInputByLabelText("UTM Campaign");
  var utmMediumField = findInputByLabelText("UTM Medium");
  var utmContentField = findInputByLabelText("UTM Content");

  // Populate the form fields with correct values
  if (referrerUrlField) referrerUrlField.value = sessionReferrer || "N/A";
  if (sessionLandingField) sessionLandingField.value = sessionLanding || "N/A";
  if (utmSourceField) utmSourceField.value = sessionUtmSource || "N/A";
  if (utmCampaignField) utmCampaignField.value = sessionUtmCampaign || "N/A";
  if (utmMediumField) utmMediumField.value = sessionUtmMedium || "N/A";
  if (utmContentField) utmContentField.value = sessionUtmContent || "N/A";

  // Lead Channel Rules

// Default rule numbers
var defaultRuleNumber = 916780011;
var sessionRuleNumber = defaultRuleNumber;

// Initialize flag variables
var sessionRuleMatched = false;

// Rule 1: Organic social
if (!sessionRuleMatched &&
    (
        (sessionUtmMedium === "social" && recognizedSocialNetworks.includes(sessionUtmSource)) ||
        (sessionUtmMedium === "social" && socialMediaSites.some(function(site) { return sessionDomain.includes(site); }))
    )
) {
    sessionRuleNumber = 916780007;
    sessionRuleMatched = true;
}

// Rule 2: Email marketing
if (!sessionRuleMatched && sessionUtmMedium === "email") {
    sessionRuleNumber = 916780001;
    sessionRuleMatched = true;
}

// Rule 3: SMS marketing
if (
  !sessionRuleMatched &&
  (
    sessionUtmSource === "sms" ||
    sessionUtmMedium === "sms"
  )
) {
  sessionRuleNumber = 916780001;
  sessionRuleMatched = true;
}

// Rule 4: Paid social
if (!sessionRuleMatched &&
    ((sessionUtmMedium && (sessionUtmMedium === "paid" || sessionUtmMedium === "ppc" || sessionUtmMedium === "cpc")) &&
    (sessionUtmSource && (recognizedSocialNetworks.includes(sessionUtmSource) || socialMediaSites.includes(sessionDomain))))
) {
    sessionRuleNumber = 916780002;
    sessionRuleMatched = true;
}

// Rule 5: Paid search
if (!sessionRuleMatched &&
    (
        (sessionUtmSource && sessionUtmSource.includes("paidsearch")) ||
        (sessionUtmMedium && sessionUtmMedium.includes("adwords")) ||
        (sessionUtmMedium && sessionUtmMedium.includes("ppc")) ||
        (sessionUtmCampaign && sessionUtmCampaign.includes("adwords")) ||
        sessionParams.searchParams.has("gclid") ||
        ((sessionParams.searchParams.has("utm_source") || sessionParams.searchParams.has("utm_medium") || sessionParams.searchParams.has("utm_campaign")) &&
          searchEngines.includes(sessionDomain))
    )
) {
    sessionRuleNumber = 916780003;
    sessionRuleMatched = true;  // Add this line to mark the rule as matched
}

  // Rule 6: Display
  if (!sessionRuleMatched && sessionUtmMedium === "display") {
    sessionRuleNumber = 916780004;
sessionRuleMatched = true;
  }

  // Rule 7: Affiliates
  if (!sessionRuleMatched && sessionUtmMedium && (sessionUtmMedium.includes('affiliate') || sessionUtmMedium.includes('affiliates'))) {
    sessionRuleNumber = 916780005;
sessionRuleMatched = true;
  }

  // Rule 8: Other campaigns
 var sessionSourceParams = [sessionUtmSource, sessionUtmMedium, sessionUtmCampaign, sessionParams.searchParams.get('source')];
  var sessionContainsExcludedWords = excludeWords.some(function (word) {
    return sessionSourceParams.some(function (param) {
      return param && param.toLowerCase().includes(word);
    });
  });
  if (!sessionRuleMatched && sessionContainsExcludedWords) {
    sessionRuleNumber = 916780006;
sessionRuleMatched = true;
  }

  // Rule 9: Organic search
if (!sessionRuleMatched && searchEngines.some(function(engine) {
    return sessionDomain.includes(engine);
})) {
    sessionRuleNumber = 916780008;
    sessionRuleMatched = true;
}

// Rule 10: Referral
if (!sessionRuleMatched && !socialMediaSites.includes(sessionDomain) && !searchEngines.includes(sessionDomain) && sessionDomain !== 'N/A') {
  sessionRuleNumber = 916780009;
sessionRuleMatched = true;
}
// Rule 11: Direct
if (!sessionRuleMatched && sessionDomain === 'N/A') {
    sessionRuleNumber = 916780010;

}

// Return an object containing session rule numbers
return {
    sessionChannel: sessionRuleNumber,
};

}

// Results
document.addEventListener("d365mkt-afterformload", function () {
  var rulesResult = processRulesAndPopulateFields(sessionStorage.getItem('session_landing'));
  console.log("d365mkt-afterformload", "Session Channel:", rulesResult.sessionChannel);
  // Populate Session Lead Channel
  var sessionLeadChannelField = findInputByLabelText("Lead Channel");
  if (sessionLeadChannelField) {
    sessionLeadChannelField.value = rulesResult.sessionChannel;
  }
});
</script>

Final Steps

After adding all of the variables, triggers and tags to Google Tag Manager, preview your changes to make sure everything is working as expected. If set up correctly, when reviewing in the GTM preview, you should see that the rtmFormExists event fires after the Container Loaded built in trigger. On that step, you should see that the two tags fired for RTM – Lead Channel Capture and then RTM – D365 RTM Form Loader. Fill out your form and then review the new Lead in D365 to make sure your hidden fields were populated.

Once working, Publish the changes in Google Tag Manager.

Each time a new RTM Form needs to be added to your website, be sure to remove the bottom script part below, and do not include it along with the rest of the details.

<script src=’https://cxppusa1formui01cdnsa01-endpoint.azureedge.net/global/FormLoader/FormLoader.bundle.js’></script>

Then make sure to add this script below the remaining form code on your webpage:

<script>
    window.dataLayer = window.dataLayer || [];
    window.dataLayer.push({
        'event': 'rtmFormExists'
    });
</script>

Here we can see all of the fields captured on a Lead in D365, where this one did include UTM parameters, and all of the rules were checked until it hit a match for the Lead Channel of Referral. In this instance, I could add linktree to the list of social networks in the Lead Channel, then moving forward, it would be flagged as Organic Social instead.

Click to view in detail

In this instance, there were no UTM Parameters in the landing page URL, but the referrer of Google was found in the list of search engines, so the Lead Channel was set as Organic search.

Click to view in detail

When the person closes their browser, this will clear the sessionStorage, so the process will start again next time they visit your website. Hope this helps!


Check out the latest post:
Auto Assign Leads Without Code Using Lead Scoring & Work Assignment


D365 Marketing Weekly
Have you seen the D365 Marketing Weekly newsletter yet?
A weekly issue covering features, functionality and news on the topic of Marketing, specifically covering Dynamics 365 Marketing and other interesting tools and tips for anyone interested in the subject.
Subscribe Here
This is just 1 of 477 articles. You can browse through all of them by going to the main blog page, or navigate through different categories to find more content you are interested in. You can also subscribe and get new blog posts emailed to you directly.




2 thoughts on “Capture Lead Channel Traffic Source Without UTM Parameters

  1. Hi Megan,

    I have to enter “https://cxppusa1formui01cdnsa01-endpoint.azureedge.net” as approved script source in the security headers for it to run. Are we vulnerable for attacks where people can run scripts from that site if they have access to publish content on our website?

    Thank you!

    1. Hi Marie, not quite sure I understand the question fully. The part where you have said ‘people can run scripts from that site if they have access to publish content on your website’. Are you saying you have given access to 3rd party agencies and worried they can add scripts to your site, or something different?

Leave a Reply

Your email address will not be published. Required fields are marked *