Tracking Personalization Web Campaign Statistics

The Campaign Stats Gear tracks impressions, clickthroughs, dismissals, and recommended items on your Marketing Cloud Personalization campaigns. These statistics give you insight into the effectiveness of these campaigns and help you decide where to adjust your campaigns for better results.

Most organizations use third-party analytics software to track campaign effectiveness, in addition to using Personalization Campaign Statistics. In such cases, it is possible that Personalization campaign statistics differ from statistics gathered via third-party software. This difference is mainly because tracking parameters and their definitions on Personalization can vary from how they’re defined, handled, and tracked in third-party systems.

Note

A campaign’s DOM structure must meet specific requirements to correctly track statistics in Personalization.

Background 

Campaign statistics are necessary for analytics in Personalization, analytics events emitted to external systems, segment creation, campaign targeting, and machine learning optimization.

HTML Specification 

Use the following HTML data attributes in order to properly track campaign statistics using the Campaign Statistics gear.

AttributeDescriptionUse in Template Handlebars
data-evg-campaign-idThe ID of the campaign. This attribute must be specified for any client-rendered web campaigns.data-evg-campaign-id="{{campaign}}"
data-evg-experience-idThe ID of the experience. This attribute must be specified for any client-rendered web campaigns. By default, clicks on any <a> tags nested within an element containing this attribute are tracked.data-evg-experience-id="{{experience}}"
data-evg-user-groupThe group that the user belongs to. This group is either the test or control group.data-evg-user-group="{{userGroup}}"
data-evg-clickthroughIndicates that this element is clickable. This attribute can be added to any element with no value to indicate that it should generate a clickthrough when clicked. Note that it is unnecessary to add this value to <a> tags that are children of an element with the data-evg-experience-id element.data-evg-clickthrough
data-evg-ignore-clickthroughIndicates that this element is clickable, but should not have a clickthrough tracked for it. This attribute can be added with no value to any element or ancestor of the clickable element.data-evg-ignore-clickthrough
data-evg-dismissalIndicates that this element is dismissible. This attribute can be added to any element with no value to indicate that it should generate a dismissal when clicked.data-evg-dismissal
data-evg-item-idThe ID of the item represented by this element and its child elements. This ID (and the type specified by data-evg-item-type) must already be tracked in the Personalization catalog. Adding this attribute to an element, along with data-evg-item-type, binds and tracks clickthroughs.data-evg-item-id="{{id}}"
data-evg-item-typeThe type of the item represented by this element and its child elements. This type (and the ID specified by data-evg-item-id), must already be tracked in the Personalization catalog. Adding this attribute to an element, along with data-evg-item-id, binds and tracks clickthroughs.data-evg-item-type="{{itemType}}"
  • To track promotions accurately using this gear, your campaign template must include the data-evg-item-id and data-evg-item-type attributes. The updated Einstein Decisions template includes these attributes by default. Cloning this template and applying it to your campaign enables promotion tracking by default.
  • Personalization will display campaign statistics for unpublished campaigns that you’re testing. Although unpublished, the Campaign Stats Gear can still track and increment campaign statistics on these campaigns.

Note

Example for tracking a clickthrough (Handlebars) 

1<!-- Default click tracking using anchor tag -->
2<div
3  id="evg-home-hero-promo"
4  data-evg-campaign-id="{{campaign}}"
5  data-evg-experience-id="{{experience}}"
6  data-evg-user-group="{{userGroup}}"
7>
8  <div class="evg-content">
9    <div class="evg-promo">
10      {{header}}
11    </div>
12    <div class="evg-body">
13      {{bodyText}}
14    </div>
15    <a class="evg-cta" href="{{destinationURL}}">
16      {{ctaText}}
17    </a>
18  </div>
19</div>
20
21<!-- Click tracking using data-evg-clickthrough -->
22<div
23  id="evg-home-hero-promo"
24  data-evg-campaign-id="{{campaign}}"
25  data-evg-experience-id="{{experience}}"
26  data-evg-user-group="{{userGroup}}"
27>
28  <div class="evg-content">
29    <div class="evg-promo">
30      {{header}}
31    </div>
32    <div class="evg-body">
33      {{bodyText}}
34    </div>
35    <button class="evg-cta" data-evg-clickthrough>
36      {{ctaText}}
37    </button>
38  </div>
39</div>

Examples for disabling a clickthrough without tracking a dismissal (Handlebars) 

1<!-- Directly on the clickable -->
2<div
3  id="evg-home-hero-promo"
4  data-evg-campaign-id="{{campaign}}"
5  data-evg-experience-id="{{experience}}"
6  data-evg-user-group="{{userGroup}}"
7>
8  <div class="evg-content">
9    <div class="evg-promo">
10      {{header}}
11    </div>
12    <div class="evg-body">
13      {{bodyText}}
14    </div>
15    <a class="evg-cta" data-evg-ignore-clickthrough href="{{destinationURL}}">
16      {{ctaText}}
17    </a>
18  </div>
19</div>
20
21<!-- On parent element of the clickable element -->
22<div
23  id="evg-home-hero-promo"
24  data-evg-campaign-id="{{campaign}}"
25  data-evg-experience-id="{{experience}}"
26  data-evg-user-group="{{userGroup}}"
27>
28  <div class="evg-content" data-evg-ignore-clickthrough>
29    <div class="evg-promo">
30      {{header}}
31    </div>
32    <div class="evg-body">
33      {{bodyText}}
34    </div>
35    <a class="evg-cta" href="{{destinationURL}}">
36      {{ctaText}}
37    </a>
38  </div>
39</div>

Example for tracking a dismissal (Handlebars) 

1<div
2  id="evg-email-capture-popup"
3  data-evg-campaign-id="{{campaign}}"
4  data-evg-experience-id="{{experience}}"
5  data-evg-user-group="{{userGroup}}"
6>
7  <div
8    class="evg-popup"
9    style="background-image:url('{{imageUrl}}'); color: {{textColor.hex}}; font-family: {{font}}"
10  >
11    <div class="evg-close" data-evg-dismissal>
12    </div>
13  </div>
14</div>

Example for a recommendation campaign (Handlebars) 

1<div id="evg-product-recs" data-evg-campaign-id="{{campaign}}" data-evg-experience-id="{{experience}}" data-evg-user-group="{{userGroup}}">
2    <div class="evg-recs-title">
3        {{title}}
4    </div>
5    {{#each items}}
6    <div class="evg-item" data-evg-item-id="{{id}}" data-evg-item-type="{{../itemType}}">
7        <a href="{{attributes.url.value}}">
8            <img src="{{attributes.imageUrl.value}}" alt="{{attributes.name.value}}"/>
9        </a>
10            <div>
11                <span><a href="{{attributes.url.value}}">{{attributes.name.value}}</a></span>
12            </div>
13        <div>
14            ${{attributes.price.value}}
15        </div>
16    </div>
17    {{/each}}
18</div>

Use with Handlebars and Client-side Code 

When a campaign is returned to the web browser, the user is in either a “Test” group or the “Control” group. If the user is in a “Test” group, a template’s client-side code executes the apply() function. Likewise, if the user is in the “Control” group, a template’s client-side code executes the control() function. Both functions can return either a Promise or void. For more information, see the Template Client JavaScript documentation.

If the apply() function doesn’t return a Promise, Personalization dispatches the Evergage.CustomEvents.OnTemplateDisplayEnd event immediately, subsequently triggering the Campaign Stats Gear to begin tracking campaign statistics even before the template may have fully rendered.

For more information on the apply() function, refer to the Template Client JavaScript documentation.

The “Control” experience does not necessarily need to render HTML into the DOM for Campaign Stats Gear to track a “Control” impression. For instance, an impression stat is tracked immediately if the control() function does not return a Promise.

Note

The following code samples are examples of how you can return a Promise in an apply() function using the pageElementLoaded method from DisplayUtils in the SalesforceInteractions and Evergage namespaces.

SalesforceInteractions Namespace
1function apply(context, template) {
2  const contentZoneSelector = SalesforceInteractions.mcis.getContentZoneSelector(
3    context.contentZone,
4  );
5
6  return SalesforceInteractions.DisplayUtils.bind(buildBindId(context))
7    .pageElementLoaded(contentZoneSelector)
8    .then((element) => {
9      const html = template(context);
10      SalesforceInteractions.cashDom(element).html(html);
11    });
12}
Evergage Namespace
1function apply(context, template) {
2  const contentZoneSelector = Evergage.getContentZoneSelector(context.contentZone);
3
4  return Evergage.DisplayUtils.bind(buildBindId(context))
5    .pageElementLoaded(contentZoneSelector)
6    .then((element) => {
7      const html = template(context);
8      Evergage.cashDom(element).html(html);
9    });
10}

The following code samples are examples of how you can return a Promise in an apply() function using a new Promise.

SalesforceInteractions Namespace
1function apply(context, template) {
2  const contentZoneSelector = SalesforceInteractions.mcis.getContentZoneSelector(
3    context.contentZone,
4  );
5
6  return new Promise((resolve) => {
7    const startTime = Date.now();
8    const contentZoneElement = () => SalesforceInteractions.cashDom(contentZoneSelector);
9
10    let tryInterval = setInterval(() => {
11      if (contentZoneElement().length > 0) {
12        clearInterval(tryInterval);
13        resolve(contentZoneElement());
14      }
15    }, 100);
16  }).then((element) => {
17    if (element == null) return; // if we remove the timeout case, this can be removed too
18
19    const html = template(context);
20    SalesforceInteractions.cashDom(element).html(html);
21  });
22}
Evergage Namespace
1function apply(context, template) {
2  const contentZoneSelector = Evergage.getContentZoneSelector(context.contentZone);
3
4  return new Promise((resolve) => {
5    const startTime = Date.now();
6    const contentZoneElement = () => Evergage.cashDom(contentZoneSelector);
7
8    let tryInterval = setInterval(() => {
9      if (contentZoneElement().length > 0) {
10        clearInterval(tryInterval);
11        resolve(contentZoneElement());
12      }
13    }, 100);
14  }).then((element) => {
15    if (element == null) return; // if we remove the timeout case, this can be removed too
16
17    const html = template(context);
18    Evergage.cashDom(element).html(html);
19  });
20}

Use without Handlebars and Client-side Code 

If your site does not use Handlebars and Client-side Code in a Personalization template to render campaigns, you can still use the Campaign Stats Gear to track campaign statistics.

When you are ready to track an impression for a Personalization campaign, dispatch an SalesforceInteractions.mcis.CustomEvents.OnTemplateDisplayEnd event or Evergage.CustomEvents.OnTemplateDisplayEnd event, depending on the SDK namespace you’re using, with the following detail.

1{
2  payload: {
3      campaign: string,
4      experience: string,
5      userGroup: string
6  }
7}

The following code samples depict how you can dispatch a SalesforceInteractions.mcis.CustomEvents.OnTemplateDisplayEnd or Evergage.CustomEvents.OnTemplateDisplayEnd event with an example payload.

SalesforceInteractions Namespace
1document.dispatchEvent(
2  new CustomEvent(window.SalesforceInteractions.mcis.CustomEvents.OnTemplateDisplayEnd, {
3    detail: {
4      payload: {
5        campaign: "abc12",
6        experience: "def34",
7        userGroup: "Test",
8      },
9    },
10  }),
11);
Evergage Namespace
1document.dispatchEvent(
2  new CustomEvent(window.Evergage.CustomEvents.OnTemplateDisplayEnd, {
3    detail: {
4      payload: {
5        campaign: "abc12",
6        experience: "def34",
7        userGroup: "Test",
8      },
9    },
10  }),
11);

Control Click Tracking 

In order to track clicks in the control group, the data attributes must be added to elements that are either rendered by the campaign, or exist on site and a user in the control group would see. See the following code samples for guidance on adding these attributes in the control() function.

SalesforceInteractions Namespace
1function control(context) {
2  SalesforceInteractions.cashDom("SELECTOR").attr({
3    "data-evg-campaign-id": context.campaign,
4    "data-evg-experience-id": context.experience,
5    "data-evg-user-group": context.userGroup,
6  });
7}
Evergage Namespace
1function control(context) {
2  Evergage.cashDom("SELECTOR").attr({
3    "data-evg-campaign-id": context.campaign,
4    "data-evg-experience-id": context.experience,
5    "data-evg-user-group": context.userGroup,
6  });
7}

If you’re using a DisplayUtil such as pageElementLoaded in the template’s apply function, then it is also strongly suggested that you use it similarly in the control function to ensure that campaign stats are being tracked the same way.

SalesforceInteractions Namespace
1function control(context) {
2  const contentZoneSelector = SalesforceInteractions.mcis.getContentZone(context.contentZone);
3  return SalesforceInteractions.DisplayUtils.bind(`${context.campaign}:${context.experience}`)
4    .pageElementLoaded(contentZoneSelector)
5    .then((element) => {
6      SalesforceInteractions.cashDom("SELECTOR").attr({
7        "data-evg-campaign-id": context.campaign,
8        "data-evg-experience-id": context.experience,
9        "data-evg-user-group": context.userGroup,
10      });
11    });
12}
Evergage Namespace
1function control(context) {
2  const contentZoneSelector = Evergage.getContentZone(context.contentZone);
3  return Evergage.DisplayUtils.bind(`${context.campaign}:${context.experience}`)
4    .pageElementLoaded(contentZoneSelector)
5    .then((element) => {
6      Evergage.cashDom("SELECTOR").attr({
7        "data-evg-campaign-id": context.campaign,
8        "data-evg-experience-id": context.experience,
9        "data-evg-user-group": context.userGroup,
10      });
11    });
12}

Sending Campaign Stats without the Campaign Stats Gear 

SalesforceInteractions.mcis.sendStat and Evergage.sendStat accept a CampaignStatEvent.

CampaignStatEvent 

1{
2  campaignStats: CampaignStat[];
3}

CampaignStat 

1{
2  experienceId: string,
3  stat: 'Impression' | 'Clickthrough' | 'Dismissal',
4  control: boolean,
5  catalog: { [itemTypeKey: string]: string[] }
6}

The following code samples depict how you can use SalesforceInteractions.mcis.sendStat or Evergage.sendStat to send a CampaignStatEvent event with recommended items.

SalesforceInteractions Namespace
1const stat = {
2  experienceId: "def34",
3  stat: "Impression",
4  control: false,
5  catalog: {
6    Product: ["product1", "product2"],
7  },
8};
9
10SalesforceInteractions.mcis.sendStat({ campaignStats: [stat] });
Evergage Namespace
1const stat = {
2  experienceId: "def34",
3  stat: "Impression",
4  control: false,
5  catalog: {
6    Product: ["product1", "product2"],
7  },
8};
9
10Evergage.sendStat({ campaignStats: [stat] });