<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Anna Bastron</title>
    <description>The latest articles on DEV Community by Anna Bastron (@annagevel).</description>
    <link>https://dev.to/annagevel</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1002310%2Fea69c7a5-1133-4802-b3ee-013eb48c3967.jpeg</url>
      <title>DEV Community: Anna Bastron</title>
      <link>https://dev.to/annagevel</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/annagevel"/>
    <language>en</language>
    <item>
      <title>Learnings from using the Sitecore ADM module</title>
      <dc:creator>Anna Bastron</dc:creator>
      <pubDate>Tue, 26 Nov 2024 11:00:51 +0000</pubDate>
      <link>https://dev.to/byteminds_agency/learnings-from-using-the-sitecore-adm-module-9gc</link>
      <guid>https://dev.to/byteminds_agency/learnings-from-using-the-sitecore-adm-module-9gc</guid>
      <description>&lt;p&gt;If you worked with a Sitecore XP website that has been live for a long time, I am sure you are familiar with the xDB cleanup requirements. I usually recommend to agree xDB data retention and set up the clean up process from day 1, but it is not always possible. Some websites were developed on an older version of Sitecore that did not have the built-in functionality for data removal, or the volume of xDB data was underestimated, or maybe tracking was enabled later down the line without considering data retention. &lt;/p&gt;

&lt;p&gt;This is a common issue and the exact reason why &lt;a href="https://support.sitecore.com/kb?id=kb_article_view&amp;amp;sysparm_article=KB0232559" rel="noopener noreferrer"&gt;Sitecore Analytics Database Manager (ADM) module&lt;/a&gt; exists. It allows viewing xDB records statistics and removing collection data (raw analytical data collected by Sitecore XP) via Sitecore UI while keeping the data integrity.&lt;/p&gt;

&lt;p&gt;However, when I tried to use ADM for the first time on Sitecore 9 and Sitecore 10 websites, I faced a few challenges, particularly when dealing with large volumes of old data. In this article, we’ll explore these challenges and look at optimisation strategies for efficient data cleanup.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Sitecore ADM works
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcnd9oadmavtz9scw0i1q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcnd9oadmavtz9scw0i1q.png" alt="High level overview of ADM process" width="800" height="366"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One thing that is critical to understand is that any ADM cleanup process consists of two key phases: task generation and task processing.&lt;/p&gt;

&lt;h4&gt;
  
  
  Task generation
&lt;/h4&gt;

&lt;p&gt;During this phase, the module retrieves all contacts from the xDB regardless of the date range specified by the user. It begins processing the most recent contacts first and systematically works backward through the database. The module reads additional data associated with each contact, such as interactions and facets, to determine whether a record meets the deletion criteria. If a contact qualifies for deletion, its ID is stored in the &lt;code&gt;[Tasks]&lt;/code&gt; table of the &lt;code&gt;[ADM.Tasks]&lt;/code&gt; database for later task processing. &lt;/p&gt;

&lt;h4&gt;
  
  
  Task processing
&lt;/h4&gt;

&lt;p&gt;In the second phase, the module processes the tasks generated in the previous step, deleting all records marked for removal.&lt;/p&gt;

&lt;p&gt;While this approach seems robust at first, it comes with a few significant limitations, particularly around the performance and scalability of the task generation phase.&lt;/p&gt;

&lt;h2&gt;
  
  
  Challenges
&lt;/h2&gt;

&lt;h4&gt;
  
  
  1. Inefficient SQL queries
&lt;/h4&gt;

&lt;p&gt;One of the main challenges is the way how the ADM module works with xDB Shard databases. Instead of relying on SQL indexes to efficiently filter contacts by date, the module retrieves all contacts and iterates through them, starting with the most recent. This results in slower data retrieval times, especially when working with large datasets spanning several years.&lt;/p&gt;

&lt;p&gt;For instance, even if you specify a short date range in 2022, the module will still need to process and check all contacts, including those from 2023-2024. This significantly increases the load on the shard databases, leading to higher data input/output operations and longer processing times.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Threading and timeout issues
&lt;/h4&gt;

&lt;p&gt;By default, the ADM module operates with 4 threads and processes 1,000 contacts per batch. While these settings may be sufficient for smaller databases, you may want to tweak these settings if you have a larger database.&lt;/p&gt;

&lt;p&gt;The module’s batching system divides work between threads at the start of the process, meaning that if there is an SQL timeout in one of the threads, the entire thread will be aborted and the cleanup of this batch will not be completed. Therefore, the same process will have to be restarted from scratch to pick up remaining DB records. This can be frustrating, particularly when processing large datasets that take several hours to be processed.&lt;/p&gt;

&lt;h4&gt;
  
  
  3. Single task limit
&lt;/h4&gt;

&lt;p&gt;Another challenge is that only one cleanup process can run at a time. Attempting to create a new task while another is running will terminate the existing process, which makes it critical to monitor and manage the process carefully.&lt;/p&gt;

&lt;h4&gt;
  
  
  4. Limited pause functionality
&lt;/h4&gt;

&lt;p&gt;The task generation phase cannot be paused, it is supported only for task processing. There was an idea to run the cleanup in batches outside of business hours for one of the websites with a lot of xDB data to remove, but it was impossible to do run task generation in batches because it cannot be paused and it had to be done in one go.&lt;/p&gt;

&lt;h2&gt;
  
  
  Optimisation tactics
&lt;/h2&gt;

&lt;p&gt;Here are some tricks that helped me to reduce processing times and achieve the best performance for the cleanup in the past:&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Read documentation
&lt;/h4&gt;

&lt;p&gt;I know this sounds obvious, but if you plan to use ADM and have not looked at the documentation that comes with it, do it now! It is quite technical and can answer many questions you already have.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Upscale to Premium tier
&lt;/h4&gt;

&lt;p&gt;If your server or database resources are under strain, especially &lt;code&gt;Shards&lt;/code&gt; and &lt;code&gt;ProcessingPools&lt;/code&gt; databases, you can try temporarily  upscaling them for the cleanup. Premium Azure SKUs are optimised for high data I/O, which is critical when working with the ADM intensive data processing.&lt;/p&gt;

&lt;h4&gt;
  
  
  3. Adjust performance settings
&lt;/h4&gt;

&lt;p&gt;There is some flexibility in tweaking the performance of the ADM module. You can increase multiple &lt;code&gt;NumberOfThreads&lt;/code&gt;, &lt;code&gt;RetrieveDataBatchSize&lt;/code&gt; and &lt;code&gt;NumberOfConnectionRetries&lt;/code&gt; settings in the ADM configuration files (see section "6. Performance Tuning" in the ADM module documentation). This helps speed up task generation and processing but should be balanced against server and database available resources.&lt;/p&gt;

&lt;h4&gt;
  
  
  4. Measure and plan accordingly
&lt;/h4&gt;

&lt;p&gt;Although tempting, splitting the cleanup process into smaller date ranges may not necessarily improve performance. The ADM module will still read all contacts every time, so this strategy may not be the most efficient for some cases. Instead, allow the process to finish at least one time and monitor its completion, note the time taken to better estimate and plan additional runs.&lt;/p&gt;

&lt;p&gt;Also, if you plan to perform the cleanup in multiple runs, start with the recent date ranges because they will be accessed first during the task generation phase.&lt;/p&gt;

&lt;h4&gt;
  
  
  5. Consider direct SQL queries
&lt;/h4&gt;

&lt;p&gt;For older versions of Sitecore, or if you struggle to run ADM process on your xDB Shards even after tweaking performance settings, an alternative approach could be used - running direct SQL queries to clean up data (you can find a few useful scripts &lt;a href="https://github.com/geann/Sitecore-xDB-cleanup-scripts/" rel="noopener noreferrer"&gt;here&lt;/a&gt;). However, this method requires understanding of SQL scripts and xDB tables structure, so do this only if you have backed up your Shard DBs and you are confident in your skills.&lt;/p&gt;




&lt;p&gt;Sitecore xDB cleanup is not always simple and the ADM module can be useful tool for managing and cleaning up contact and interaction data. I hope this article helped you to understand how the module works, its limitations and tactics for optimising its performance.&lt;/p&gt;

</description>
      <category>sitecore</category>
      <category>xdb</category>
      <category>sitecoreadm</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How personalisation works in Sitecore XM Cloud</title>
      <dc:creator>Anna Bastron</dc:creator>
      <pubDate>Thu, 11 Jul 2024 11:07:43 +0000</pubDate>
      <link>https://dev.to/byteminds_agency/how-personalisation-works-in-sitecore-xm-cloud-52o4</link>
      <guid>https://dev.to/byteminds_agency/how-personalisation-works-in-sitecore-xm-cloud-52o4</guid>
      <description>&lt;p&gt;In my previous article, I shared a comprehensive &lt;a href="https://dev.to/byteminds_agency/troubleshooting-tracking-and-personalisation-in-sitecore-xm-cloud-2n6"&gt;troubleshooting guide for Sitecore XM Cloud tracking and personalisation&lt;/a&gt;. The guide addresses common issues, explains investigation steps and provides solutions for these issues. &lt;/p&gt;

&lt;p&gt;However, understanding how the personalisation engine works in depth can further help in diagnosing persistent issues and developing personalised websites. This article visualises what happens behind the scenes when you enable personalisation and tracking in your Sitecore XM Cloud applications.&lt;/p&gt;

&lt;h2&gt;
  
  
  Overview of the personalisation workflow
&lt;/h2&gt;

&lt;p&gt;Before we start, let's familiarise ourselves with key elements of the diagram we are going to look at.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmqidbrg3lvgkvmbbwumt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmqidbrg3lvgkvmbbwumt.png" alt="Key elements of personalisation and tracking data flows" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;On the left hand site we can see the &lt;strong&gt;Browser&lt;/strong&gt;, it is responsible for sending requests to our application and displaying the result to end users.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;strong&gt;JSS app&lt;/strong&gt; sits at the top centre of the diagram and it represents the Rendering Host role in Sitecore Headless topology. It is the application that processes incoming requests and handles the presentation layer. In this case it is a Next.js application based on the &lt;a href="https://doc.sitecore.com/xmc/en/developers/xm-cloud/getting-started-with-xm-cloud.html" rel="noopener noreferrer"&gt;XM Cloud foundation template&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Edge / XM API&lt;/strong&gt; is shown on the right hand side, it is a GraphQL endpoint that returns layout definition and content for requested pages, including &lt;a href="https://doc.sitecore.com/xmc/en/users/xm-cloud/create-a-page-variant.html" rel="noopener noreferrer"&gt;personalised variants&lt;/a&gt;. It can be Experience Edge API for cloud-based setups or a local CM container API endpoint for development purposes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Finally, at the bottom we can see &lt;strong&gt;Tracking &amp;amp; Interactive API&lt;/strong&gt; powered by the embedded instance of Sitecore CDP &amp;amp; Personalize. This is where &lt;a href="https://doc.sitecore.com/xmc/en/users/xm-cloud/creating-an-audience.html" rel="noopener noreferrer"&gt;audiences&lt;/a&gt; are stored, &lt;a href="https://doc.sitecore.com/xmc/en/users/xm-cloud/specifying-variables-for-conditions.html" rel="noopener noreferrer"&gt;conditions&lt;/a&gt; are executed and &lt;a href="https://doc.sitecore.com/xmc/en/users/xm-cloud/analyze.html" rel="noopener noreferrer"&gt;analytics&lt;/a&gt; is collected.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When we talk about Next.js applications, there are two main rendering methods: &lt;strong&gt;Server-Side Rendering (SSR)&lt;/strong&gt; and &lt;strong&gt;Static Site Generation (SSG)&lt;/strong&gt;. Sitecore XM Cloud personalisation engine works slightly differently with these two approaches so we will cover both of them to understand their specifics.&lt;/p&gt;

&lt;h2&gt;
  
  
  Server-Side Rendering
&lt;/h2&gt;

&lt;p&gt;This is what happens when a website visitor opens a page that has personalised variants.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1.&lt;/strong&gt; When a user loads the website or navigates to a new page, the browser sends an HTTP request to the Next.js application, including cookies and HTTP headers that can be used in personalisation conditions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuw5fqmuxes3iuj20gbjg.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuw5fqmuxes3iuj20gbjg.gif" alt="Step 1. Browser request" width="1200" height="675"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2.&lt;/strong&gt; The Next.js application runs all registered middleware modules, with the &lt;a href="https://doc.sitecore.com/xmc/en/developers/jss/220/jss-xmc/personalization-in-jss-next-js-applications.html" rel="noopener noreferrer"&gt;Personalize middleware&lt;/a&gt; being of particular interest. This middleware sends an API request to the GraphQL endpoint to fetch all personalised variants for the current page configured in the CMS.&lt;/p&gt;

&lt;p&gt;If there are no personalised variants configured for this page or the page is not found, this middleware will exit and page generation will continue as usual.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flgaxkpr6kk7eqotdqfoo.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flgaxkpr6kk7eqotdqfoo.gif" alt="Step 2. Personalize middleware kicks in and sends a request to Edge / XM API" width="1000" height="563"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3.&lt;/strong&gt; If a page has more than one variant, the middleware sends another API request to the Personalize API to detect if the current visitor matches any of the audiences configured for this page. This is where cookies and HTTP headers received from the browser will help as they will be passed to the Personalize API to identify the visitor.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6nqvutp4r94y2ks8wrj1.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6nqvutp4r94y2ks8wrj1.gif" alt="Step 3. Identifying the audience for the current visitor" width="1000" height="563"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4.&lt;/strong&gt; By combining responses from these two API requests, the middleware determines which personalised page variant is suitable for the current visitor (cat-themed page in the diagram below 😺).&lt;/p&gt;

&lt;p&gt;If the visitor matches an audience configured for the page, the middleware will rewrite the page path to a special personalised variant path (for example, from &lt;code&gt;/_site_Test/Pets&lt;/code&gt; to &lt;code&gt;/_variantId_0dd7b00680be49c6815ca4d0793a36da/_site_Test/Pets&lt;/code&gt;) and this will instruct the Next.js application to use the specific page variant when rendering the page. So the personalised version of the page will be rendered on the server and returned to the browser. &lt;/p&gt;

&lt;p&gt;If the visitor does not match any audiences, then the default page variant will be rendered and returned to the user. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffjvnouyg4pcc1z1ym17p.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffjvnouyg4pcc1z1ym17p.gif" alt="Step 4. Returning the personalised page" width="1000" height="563"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 5.&lt;/strong&gt; Once the page is rendered in the browser, a special React component responsible for tracking will send an API request to the CDP Stream API to register the page view, including which personalised variant was shown. This data is later will be aggregated and shown in analytics reports.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3aq7ibcnf6qnd00vbk9h.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3aq7ibcnf6qnd00vbk9h.gif" alt="Step 5. Sending a page view event" width="1000" height="563"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Static Site Generation (SSG)
&lt;/h2&gt;

&lt;p&gt;The SSG process flow is similar to SSR but has some specifics related to this rendering method. Now, let's see what are these differences.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1.&lt;/strong&gt; This step is exactly the same as for SSR - the browser sends an HTTP request to the Next.js application with cookies and HTTP headers.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4w4sym8f4mn9qhluzv5l.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4w4sym8f4mn9qhluzv5l.gif" alt="Step 1. Browser request" width="1000" height="563"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2.&lt;/strong&gt; This is where things get different from the SSR process. When the Personalize middleware kicks in, it checks if there are any pre-rendered page variants for this page (the default, cat-themed 😺 and dog-themed 🐶 variants in the diagram). If yes, it skips the API request to the Edge / XM API, otherwise it will fall back to the standard SSR process and fetch personalised variants for the current page.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv4d6njbrszjvn9lglug8.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv4d6njbrszjvn9lglug8.gif" alt="Step 2. Leveraging pre-rendered page variants" width="1000" height="563"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3.&lt;/strong&gt; This step is the same as in the SSR flow - if a page has personalised variants, the middleware sends an API request to the Personalize API to identify visitor's audience.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjlhmjrprv2tk8uvcd28a.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjlhmjrprv2tk8uvcd28a.gif" alt="Step 3. Identifying the audience for the current visitor" width="1000" height="563"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4.&lt;/strong&gt; If there is a match and personalised page variants are pre-generated, the middleware will rewrite the page path and then the appropriate personalised page variant will be chosen and returned to the browser (looks like it's the cat-themed variant again! 😺). &lt;/p&gt;

&lt;p&gt;If there are no pre-generated personalised variants, but they exist in the CMS and the visitor matches one of the audiences, then the middleware will rewrite the page path, the Next.js app will generate the page variant and save the static output for future requests. This is the default process, see notes at the end of the article to learn more about static generation of personalised page variants.&lt;/p&gt;

&lt;p&gt;If the visitor does not match any audiences, then the default page variant will be returned to the user using the statically generated HTML if it exists.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2a1epfhsffuam4ekangy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2a1epfhsffuam4ekangy.gif" alt="Step 4. Returning the personalised page" width="1000" height="563"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 5.&lt;/strong&gt; As with SSR, once the page is returned to the browser the &lt;code&gt;CdpPageView&lt;/code&gt; React component will send an API request to track the page view event for reporting. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8rs3mcnlgi2b9031fl1r.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8rs3mcnlgi2b9031fl1r.gif" alt="Step 5. Sending a page view event" width="1000" height="563"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see, the flow is very similar for SSR and SSG. The SSG method with static HTML generation and skipping some API requests can give us a performance boost, especially for websites with high traffic and personalisation enabled on frequently visited pages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Notes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Personalize middleware
&lt;/h3&gt;

&lt;p&gt;The middleware is provided by Sitecore as a part of the &lt;a href="https://doc.sitecore.com/xmc/en/developers/jss/220/jss-xmc/the-jss-xm-cloud-add-on-for-next-js.html" rel="noopener noreferrer"&gt;JSS XM Cloud add-on for Next.js&lt;/a&gt;. Please note that this add-on is compatible with JSS version 21.6 and later. For earlier versions the &lt;a href="https://doc.sitecore.com/xmc/en/developers/jss/215/jss-xmc/the-next-js-personalize-add-on.html" rel="noopener noreferrer"&gt;Next.js Personalize add-on&lt;/a&gt; is used that is now obsolete. &lt;/p&gt;

&lt;p&gt;This add-on is only compatible with Sitecore XM Cloud due to specific naming conventions and pre-configured settings required for the embedded CDP and Personalize instance.&lt;/p&gt;

&lt;h3&gt;
  
  
  Build-time static generation of personalised page variants
&lt;/h3&gt;

&lt;p&gt;To expand on the step 4 of SSG process, let's see when exactly personalised page variants are generated. As you may know, hosting providers often limit the time available for SSG builds. By default, pre-generation of personalised page variants during build is disabled in to avoid long build times.&lt;/p&gt;

&lt;p&gt;However, if sufficient build time is available (for example, your website does not have too many pages) or you have critical personalisation rules on key pages (for instance, you only have a small number of personalised variants on the homepage or an important campaign page), then SSG for personalised variants &lt;a href="https://doc.sitecore.com/xmc/en/developers/jss/220/jss-xmc/walkthrough--configuring-personalization-in-a-next-js-jss-app.html#enable-static-generation-for-personalized-variants" rel="noopener noreferrer"&gt;can be explicitly enabled&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;This can be done by modifying the file &lt;code&gt;src/lib/sitemap-fetcher/plugins/graphql-sitemap-service.ts&lt;/code&gt; and setting the &lt;code&gt;includePersonalizedRoutes&lt;/code&gt; parameter to &lt;code&gt;true&lt;/code&gt; in the sitemap service constructor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;this._graphqlSitemapService = new MultisiteGraphQLSitemapService({
    clientFactory,
    sites: [...new Set(siteResolver.sites.map((site: SiteInfo) =&amp;gt; site.name))],
    includePersonalizedRoutes: true,
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Just make sure to watch your build time after enabling this setting to avoid build failing or incurring unnecessary hosting costs.&lt;/p&gt;




&lt;p&gt;Sitecore XM Cloud provides robust support for personalisation out-of-the-box for both SSR and SSG rendering methods. The add-on with Personalize middleware and CDP tracking component streamlines the process of fetching, matching, and delivering personalised content to website visitors while tracking interactions for reporting. &lt;/p&gt;

&lt;p&gt;Hope this article helps to understand the entire process of personalisation and tracking in XM Cloud and allows you to build well-performing and personalised applications. Feel free to share your thoughts and questions in the comments! &lt;/p&gt;

</description>
      <category>sitecore</category>
      <category>xmcloud</category>
      <category>personalization</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Sitecore Personalize: tips &amp; tricks for decision models and programmable nodes</title>
      <dc:creator>Anna Bastron</dc:creator>
      <pubDate>Fri, 24 Nov 2023 12:42:23 +0000</pubDate>
      <link>https://dev.to/byteminds_agency/sitecore-personalize-tips-tricks-for-decision-models-and-programmable-nodes-2emm</link>
      <guid>https://dev.to/byteminds_agency/sitecore-personalize-tips-tricks-for-decision-models-and-programmable-nodes-2emm</guid>
      <description>&lt;p&gt;While working with Sitecore Personalize, I've collected various findings around decision models and programmable nodes. Some of them were the result of a human error, while others were just specific rules and conventions I discovered while using the platform. So I'm sharing these tips and tricks in the hope that they prove as valuable to you as they did to me.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Remember to return a value from programmable nodes
&lt;/h3&gt;

&lt;p&gt;Starting with a fundamental tip: when creating custom programmable nodes, ensure you include a &lt;code&gt;return&lt;/code&gt; statement at the end of the JavaScript function. It's easy to overlook, but without it, your decision model might return unexpected results.&lt;/p&gt;

&lt;p&gt;When I added my very first custom programmable node to a decision model, it took me a while to find why the model was working incorrectly. It turned out my programmable node was calculating everything correctly, but there was no &lt;code&gt;return&lt;/code&gt; statement after calling an internal function. I don't know how I expected Sitecore Personalize to know which value to use! 😅&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb7sidp5a6jvz1n2gxacf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb7sidp5a6jvz1n2gxacf.png" alt="The " width="800" height="347"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  2. Set the correct return type in programmable nodes
&lt;/h3&gt;

&lt;p&gt;Specify the correct return type in programmable nodes, aligning it with the decision table's comparison rules. Whether it's &lt;code&gt;integer&lt;/code&gt;, &lt;code&gt;long&lt;/code&gt;, &lt;code&gt;double&lt;/code&gt;, &lt;code&gt;string&lt;/code&gt;, &lt;code&gt;boolean&lt;/code&gt;, &lt;code&gt;date&lt;/code&gt;, or &lt;code&gt;map&lt;/code&gt;, choosing the right type is necessary for seamless integration with the decision table.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo9de2hadxwtt5fluecak.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo9de2hadxwtt5fluecak.png" alt="This is the data type we will be using later for comparing values in the decision table" width="800" height="309"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  3. Avoid leaving empty lines in decision tables
&lt;/h3&gt;

&lt;p&gt;They are not as empty as I thought! Beware of seemingly harmless empty lines in decision tables. A forgotten line can lead to hit policy errors or produce incorrect output, because hyphens in input columns act as wildcards. For instance, in the example below, the first row applies to all guests of "customer" type, irrespective of their page views count.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fax26qcsccwsxlkx54owf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fax26qcsccwsxlkx54owf.png" alt="Line #4 is not empty, it will match guests of any type and with any number of events" width="800" height="487"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  4. Name decision table output columns carefully
&lt;/h3&gt;

&lt;p&gt;When naming output columns in decision tables, remember that output of the decision models can be used in Freemarker templates for web or full stack experiences. Opt for output reference names without spaces or special characters for easier integration later.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb796vqrshap8vpbsumav.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb796vqrshap8vpbsumav.png" alt="Make sure the output reference is Freemarker-friendly" width="800" height="503"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  5. Pay attention to workflow in decision model
&lt;/h3&gt;

&lt;p&gt;Before using a decision model, move at least one variant to the production state. Once in production, the variant becomes non-editable so make sure to test it before moving it to production. But don't worry, if any changes are required further down the line, you can always duplicate the live variant and update the newly created draft.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyx53upvfpul9rusi7t1o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyx53upvfpul9rusi7t1o.png" alt="Keep at least one variant in the Production column for get decision model results" width="800" height="265"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  6. Save decision model versions with comments
&lt;/h3&gt;

&lt;p&gt;When making significant changes to your decision model variant, choose the "Save with comment" button. Comments will be visible in the revision log, making it easier to track changes. They will also help finding the right version to revert to if that's required.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2g0oo2cp415mbesml16r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2g0oo2cp415mbesml16r.png" alt="Comments will help to find the right version in the revision table" width="800" height="274"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  7. Use the right case in input columns
&lt;/h3&gt;

&lt;p&gt;Be cautious with the values in decision model input columns as they are case-sensitive. Inconsistent cases may lead to unexpected behaviour, so ensure you are using the right values, especially when copying them from other sources.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq4xt99snonnvroj8sxzi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq4xt99snonnvroj8sxzi.png" alt="Input column values are case-sensitive" width="800" height="438"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  8. Use decision templates
&lt;/h3&gt;

&lt;p&gt;If there is a programmable node that you or your marketing team will likely reuse, then consider saving it as a decision template. &lt;a href="https://doc.sitecore.com/personalize/en/users/sitecore-personalize/create-a-decision-template.html" rel="noopener noreferrer"&gt;Decision templates&lt;/a&gt; allow adding configurable parameters that will be displayed as a user-friendly form and you won't need copying the same JavaScript code from one decision model to another.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F691lfro173v9cyxznuig.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F691lfro173v9cyxznuig.png" alt="Friendly UI of decision templates" width="800" height="304"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  9. Watch &lt;code&gt;executionTime&lt;/code&gt; in test canvas
&lt;/h3&gt;

&lt;p&gt;This is probably the most important recommendation from me: optimise performance of your decision models. Regardless of the type of experience and integration approach you use, long execution times will negatively affect user experience on the website and all other channels connected to Sitecore Personalize.&lt;/p&gt;

&lt;p&gt;To ensure the most efficient performance, Sitecore Personalize imposes &lt;a href="https://doc.sitecore.com/personalize/en/users/sitecore-personalize/understanding-data-limits-in-decisioning-and-conditions.html" rel="noopener noreferrer"&gt;data limits&lt;/a&gt; on the amount of data that is available in decisioning and conditions. In addition to this, you can apply the following techniques to improve execution time of programmable nodes and decision models:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Look only at the data that you need by limiting the number of sessions and events you check&lt;/li&gt;
&lt;li&gt;Break the loops and return values from functions as soon as you have found what you need to avoid accessing and iterating through unnecessary data&lt;/li&gt;
&lt;li&gt;Leverage batch segments and user data extensions where possible to make decisions based on already calculated and saved data&lt;/li&gt;
&lt;li&gt;Reduce the number of elements and connections on the decision canvas&lt;/li&gt;
&lt;li&gt;Limit the number of programmable decisions on the canvas to three or four as recommended by Sitecore. If multiple programmable decisions use the same data, consider combining them into one element.&lt;/li&gt;
&lt;li&gt;Keep the number of external data systems and AI models minimal in a decision model. Sitecore recommends to include one or two data systems and/or AI elements.&lt;/li&gt;
&lt;li&gt;Reduce the number of rows and columns in decision tables. Where possible, combine conditions by using hyphen (-) to eliminate unnecessary checks.&lt;/li&gt;
&lt;li&gt;Ensure that the model returns only one decision&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F83qg4o2rdw1nygjot2i8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F83qg4o2rdw1nygjot2i8.png" alt="Watch execution time of the model and its individual elements in the test canvas" width="800" height="445"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;I hope these insights ease your journey with Sitecore Personalize. Feel free to share your own tips or ask questions in the comments!&lt;/p&gt;

</description>
      <category>sitecore</category>
      <category>sitecorepersonalize</category>
      <category>marketing</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Custom guest profile attributes in Sitecore CDP</title>
      <dc:creator>Anna Bastron</dc:creator>
      <pubDate>Fri, 06 Oct 2023 14:23:03 +0000</pubDate>
      <link>https://dev.to/annagevel/custom-guest-profile-attributes-in-sitecore-cdp-2ibp</link>
      <guid>https://dev.to/annagevel/custom-guest-profile-attributes-in-sitecore-cdp-2ibp</guid>
      <description>&lt;p&gt;When my colleagues and I started working on a proof-of-concept for Sitecore CDP &amp;amp; Personalize, one of the important aspects of our plan was validation of how custom guest profile properties can be effectively stored and used in the CDP. &lt;br&gt;
Many organisations need to save custom pieces of data associated with website visitors and their interactions on the website. These data properties may include marketing consent status, preferred language, or specific topics of interest just to name a few. During our research we found that Sitecore CDP offers a built-in feature for storing and reading these custom fields known as &lt;strong&gt;data extensions&lt;/strong&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Types of data extensions
&lt;/h3&gt;

&lt;p&gt;Sitecore CDP supports data extension attributes for the following data models:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Guests&lt;/li&gt;
&lt;li&gt;Events&lt;/li&gt;
&lt;li&gt;Orders&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this article I will primarily focus on event and guest data extensions, but it is worth noting that similar principles apply to orders as well.&lt;/p&gt;
&lt;h3&gt;
  
  
  How to create data extensions
&lt;/h3&gt;

&lt;p&gt;Depending on your application specifics and requirements, you can choose one or multiple available APIs: Stream API, REST API and Batch API. Regardless of the integration method you select, Sitecore CDP will provide an option for sending custom data and saving it in the system for future use.&lt;/p&gt;
&lt;h4&gt;
  
  
  Stream API
&lt;/h4&gt;

&lt;p&gt;For seamless integration, &lt;a href="https://doc.sitecore.com/cdp/en/developers/api/functions.html" rel="noopener noreferrer"&gt;Engage SDK&lt;/a&gt; provides an easy way of passing &lt;strong&gt;event data extensions&lt;/strong&gt; to the CDP. The optional object &lt;code&gt;extensionData&lt;/code&gt; can be included as the last function argument when sending events, page views or identifying guests. &lt;br&gt;
Client-side tracking methods supporting the &lt;code&gt;extensionData&lt;/code&gt; parameters are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Engage.pageView(eventData[, extensionData])&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Engage.identity(eventData[, extensionData])&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Engage.event(type, eventData[, extensionData])&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Engage.addToEventQueue(type, eventData[, extensionData])&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Server-side SDK tracking methods are similar, but they also require passing the HTTP request parameter &lt;code&gt;req&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;EngageServer.pageView(eventData, req[, extensionData])&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;EngageServer.identity(eventData, req[, extensionData])&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;EngageServer.event(type, eventData, req[, extensionData])&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;



&lt;p&gt;If you opt not to use Engage SDK and prefer to integrate through direct HTTP requests, you can still supply additional data with an event by utilising the &lt;code&gt;ext&lt;/code&gt; data extension object. Please note that the name of the event data extension object must be &lt;code&gt;ext&lt;/code&gt;, otherwise Sitecore CDP will not recognise it as a data extension.&lt;/p&gt;

&lt;p&gt;Here's an example of a data extension object for HTTP requests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "type": "VIEW",
  "channel": "WEB",
  "pos": "POS name",
  "browser_id": "…",
  "language": "EN",
  "currency": "EUR",
  "page": "home",
  "ext": {
    "pageCategory": "value",
    "bonusPoints":10
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Please note that after you send the event, the data in this object becomes available in the event, but not in the guest profile.&lt;/p&gt;

&lt;p&gt;Some other limitations that are worth noting are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An event data extension object can have a maximum of 50 attributes&lt;/li&gt;
&lt;li&gt;The attribute name must be alphanumeric, i.e. it should only contain characters A-Z, a-z, or 0-9&lt;/li&gt;
&lt;li&gt;The attribute name must be unique within the entire event type&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  REST API
&lt;/h4&gt;

&lt;p&gt;If you integration requires a synchronous communication method and you can’t use Stream API, the &lt;a href="https://doc.sitecore.com/cdp/en/developers/api/rest-apis.html" rel="noopener noreferrer"&gt;REST API&lt;/a&gt; is a good alternative. It provides a special API endpoint for sending extension data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;POST /v2/guests/&amp;lt;guestRef&amp;gt;/extext
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It’s important to note that &lt;code&gt;extext&lt;/code&gt; consists of two parts: the first &lt;code&gt;ext&lt;/code&gt; is constant and means that we want to send extension data. The second &lt;code&gt;ext&lt;/code&gt; represents the name of the extension property in Sitecore CDP. The system supports multiple extension group names allowing you to send data extensions from various source systems and store them separately. Failing to do so will result in overwriting guest extension data from other sources. You can create a maximum of six guest data extension groups and use only predefined names: &lt;code&gt;ext&lt;/code&gt;, &lt;code&gt;ext1&lt;/code&gt;, &lt;code&gt;ext2&lt;/code&gt;, &lt;code&gt;ext3&lt;/code&gt;, &lt;code&gt;ext4&lt;/code&gt;, &lt;code&gt;ext5&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For instance, if you want to send extension data from a second system, you can modify the request to use the extension group &lt;code&gt;ext1&lt;/code&gt; as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;POST /v2/guests/&amp;lt;guestRef&amp;gt;/extext1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Request body example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "key": "default",
    "loyaltyTier": "level2",
    "loyaltyNumber": 12345,
    "marketingConsent": true,
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Please note that the key attribute must be set to default and guest data extension can have a maximum of 100 attributes. Naming conventions are the same as for the event extensions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unique attribute name&lt;/li&gt;
&lt;li&gt;The attribute name must be alphanumeric and written in camel case (see examples above)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;REST API also supports GET, POST and DELETE endpoints for retrieving, updating and deleting specific data extensions properties. To retrieve a data extension property from the guest profile, you can send a request like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET /v2/guests/f7aabbca-1c1b-4fc2-be72-3e16294a4f03/extext1/09bfc8cc-3815-5b77-b75a-d3cc6a867c1a
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;where &lt;code&gt;f7aabbca-1c1b-4fc2-be72-3e16294a4f03&lt;/code&gt; is the guest reference, &lt;code&gt;ext1&lt;/code&gt; is the data extension group and &lt;code&gt;09bfc8cc-3815-5b77-b75a-d3cc6a867c1a&lt;/code&gt; is the data extension property reference.&lt;/p&gt;

&lt;h4&gt;
  
  
  Batch API
&lt;/h4&gt;

&lt;p&gt;Additionally, you can send send additional data via Batch API &lt;a href="https://doc.sitecore.com/cdp/en/developers/api/send-additional-guest-data.html" rel="noopener noreferrer"&gt;https://doc.sitecore.com/cdp/en/developers/api/send-additional-guest-data.html&lt;/a&gt;. The principle is similar to the REST API, where you send data extensions using the &lt;code&gt;ext&lt;/code&gt; property. You can specify one of the six extensions group names to differentiate data from multiple systems. Batch API is particularly suitable for asynchronous integration scenarios, such as daily imports from a CRM system to synchronise customer records updated within the last 24 hours.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to use data extensions
&lt;/h3&gt;

&lt;p&gt;Now, why would you need to send some guest information to CDP as data extensions?&lt;/p&gt;

&lt;p&gt;One of the most common use cases is batch segmentation which enables creation of guest segments of guests based on their profile, event, and order extensions. This can be extremely helpful for implementing marketing automation campaigns such as sending newsletters or push notifications to specific groups of users. You can read more about batch segmentation in Sitecore CDP in my colleague’s blog post &lt;a href="https://www.linkedin.com/pulse/introduction-batch-segmentation-sitecore-cdp-kate-orlova/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Batch Segment configuration screen showing examples of customer profile fields and data extensions.&lt;/em&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgrjh8udfxg0ukbp86a9v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgrjh8udfxg0ukbp86a9v.png" alt="Batch Segment configuration screen showing examples of customer profile fields and data extensions." width="800" height="496"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;However, usage of data extensions is not limited to batch segmentation. Custom properties can be leveraged to build real-time audiences for live personalisation and A/B testing. They can be accessed within &lt;a href="https://www.linkedin.com/pulse/building-smart-decision-models-sitecore-personalize-power-anna-gevel/" rel="noopener noreferrer"&gt;programmable nodes&lt;/a&gt; in decision models and returned as a part of &lt;a href="https://www.linkedin.com/pulse/using-freemarker-template-engine-sitecore-personalize-anna-gevel/" rel="noopener noreferrer"&gt;API response in web and full stack experiences&lt;/a&gt;. Furthermore, this data can be extracted from Sitecore CDP and pushed to external systems, such as Tableau or Power BI for custom reporting.&lt;/p&gt;




&lt;p&gt;In conclusion, Sitecore CDP's data extensions feature is essential for storing additional data that will help building marketing automation and personalisation, as well as getting insights into visitor segments and interactions. &lt;/p&gt;

&lt;p&gt;By understanding how to create, manage, and read data extensions, you can harness the full potential of your customer data within Sitecore CDP &amp;amp; Personalize.&lt;/p&gt;

</description>
      <category>sitecore</category>
      <category>sitecorecdp</category>
      <category>cdp</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Server-side integration with Sitecore Personalize API</title>
      <dc:creator>Anna Bastron</dc:creator>
      <pubDate>Mon, 25 Sep 2023 08:26:51 +0000</pubDate>
      <link>https://dev.to/annagevel/server-side-integration-with-sitecore-personalize-api-4ce</link>
      <guid>https://dev.to/annagevel/server-side-integration-with-sitecore-personalize-api-4ce</guid>
      <description>&lt;p&gt;As promised, in this post I will walk you through an example of API integration with Sitecore Personalize full stack experiences. While I am using C# for demonstration purposes, keep in mind that the underlying principles are the same for applications written in JavaScript, Kotlin, Swift or any other programming language.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://dev.to/annagevel/using-freemarker-template-engine-in-sitecore-personalize-5c"&gt;my previous article&lt;/a&gt; I explained how to configure API response in Sitecore Personalize. Now, let's take the next step to read this response in our application.&lt;/p&gt;

&lt;p&gt;Imagine you have a website that requires personalisation functionality, for example displaying content tailored to the website visitors’ behaviour. Some scenarios can be achieved with web experiences and JavaScript injected on the page. However, in this example I will show you how it can be done with interactive experiences for scenarios that require a deeper level of integration between the application and personalisation engine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preparation Steps
&lt;/h2&gt;

&lt;p&gt;Before we start, I would recommend creating a Postman request for testing purposes. This allows you experiment with different request types and parameters to make sure API behaves as expected and your custom API response is correctly formatted.&lt;/p&gt;

&lt;p&gt;You should already have an interactive experience configured in Sitecore Personalize. In case you need a quick refresher, refer to the useful video guide in &lt;a href="https://www.linkedin.com/pulse/full-stack-experience-sitecore-personalize-content-kate-orlova/" rel="noopener noreferrer"&gt;this article&lt;/a&gt;. Go to your experience details and grab the friendly ID from the Setup Details section. You will also need a browser ID or an identifier of a guest that exists in CDP.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbtux2c90yne2cjifr9gp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbtux2c90yne2cjifr9gp.png" alt="Friendly ID in Sitecore CDP user interface" width="602" height="161"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With these prerequisites in place, open Postman or a similar tool and set up a POST request to &lt;code&gt;/v2/callFlows&lt;/code&gt;. The full specification for this API request is available in Sitecore documentation &lt;a href="https://doc.sitecore.com/personalize/en/developers/api/call-the-personalize-api.html" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You must include one of the following attributes: &lt;code&gt;browserId&lt;/code&gt; or &lt;code&gt;email&lt;/code&gt; or &lt;code&gt;identifiers&lt;/code&gt; to identify the guest you are sending this request for. Optionally, if your experience supports custom fields, you can include them using the &lt;code&gt;params&lt;/code&gt; object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "clientKey": "…",
    "channel": "WEB",
    "language": "en",
    "currencyCode": "EUR",
    "pointOfSale": "…",
    "browserId": "…",
    "params": {
      "someKey":"someValue"
    },
    "friendlyId": "…"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is how my test request looks in Postman:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk5ktzd4e4qrjpc24u658.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk5ktzd4e4qrjpc24u658.png" alt="Request details in Postman" width="800" height="313"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Writing Code
&lt;/h2&gt;

&lt;p&gt;Once you have verified that the API responds as expected via Postman, it is time to start developing the integration. I have set up a vanilla Sitecore website with basic Sitecore CDP tracking, generating page view events and guest profiles.&lt;/p&gt;

&lt;p&gt;API integration code consists of the following elements:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. FlowExecutionService.cs&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This class is responsible for sending API requests to &lt;code&gt;/v2/callFlows&lt;/code&gt;. I am using the standard &lt;code&gt;System.Net.Http.HttpClient&lt;/code&gt; for simplicity but you are free to choose your preferred REST API library. Here is a code snippet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public class FlowExecutionService
{
        private readonly HttpClient _httpClient;

        public FlowExecutionService(HttpClient httpClient)
        {
            _httpClient = httpClient;
            _httpClient.BaseAddress = new Uri(ConfigSettings.APIEndpoint);

        }

        public async Task&amp;lt;T&amp;gt; ExecuteFlow&amp;lt;T&amp;gt;(FlowExecutionRequest request)
        {
            using (var httpRequest = new HttpRequestMessage(HttpMethod.Post, "callFlows"))
            {
                var json = JsonConvert.SerializeObject(request);
                httpRequest.Content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");

                using (var response = await _httpClient.SendAsync(httpRequest).ConfigureAwait(false))
                {
                    var content = await response.Content.ReadAsStringAsync();
                    var result = JsonConvert.DeserializeObject&amp;lt;T&amp;gt;(content);
                    return result;
                }
            }
        }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2.&lt;/strong&gt; Then there are two model classes: &lt;strong&gt;FlowExecutionRequest.cs&lt;/strong&gt; and &lt;strong&gt;FlowExecutionResult.cs&lt;/strong&gt;. &lt;code&gt;FlowExecutionRequest&lt;/code&gt; is quite generic and can be reused for calling different experiences.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public class FlowExecutionRequest
{
        [JsonProperty("clientKey")]
        public string ClientKey { get; set; }

        [JsonProperty("channel")]
        public string Channel { get; set; }

        [JsonProperty("language")]
        public string Language { get; set; }

        [JsonProperty("currencyCode")]
        public string CurrencyCode { get; set; }

        [JsonProperty("pointOfSale")]
        public string PointOfSale { get; set; }

        [JsonProperty("email")]
        public string Email { get; set; }

        [JsonProperty("browserId")]
        public string BrowserId { get; set; }

        [JsonProperty("friendlyId")]
        public string FriendlyId { get; set; }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;FlowExecutionResult&lt;/code&gt; should be specific to your experience and match the API response your configured with FreeMarker. That’s why I defined &lt;code&gt;ExecuteFlow&amp;lt;T&amp;gt;&lt;/code&gt; as a generic method so that it can return different response models. This is just one example of how this model can look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public class FlowExecutionResult
{
        [JsonProperty("guestEmail")]
        public string GuestEmail { get; set; }

        [JsonProperty("guestName")]
        public string GuestName { get; set; }

        [JsonProperty("contentItemId")]
        public string ContentItemId { get; set; }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3.&lt;/strong&gt; Finally, models and service calls are combined in a controller. Take a look at the method &lt;code&gt;Banner()&lt;/code&gt; below, it prepares the &lt;code&gt;FlowExecutionRequest&lt;/code&gt; model, populates it with API configuration settings and passes email address of the logged-in user. If you want to use browser Id instead of email, it can be passed from the front-end code or read from the &lt;code&gt;bid_&lt;/code&gt; cookie like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BrowserId = HttpContext.Request.Cookies["bid_" + ConfigSettings.ClientKey];
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The controller calls the &lt;code&gt;FlowExecutionService&lt;/code&gt; and retrieves &lt;code&gt;FlowExecutionResult&lt;/code&gt; which in my case contains guest’s name and email address as well as personalised component data source ID. This data source is taken from the website database and passed to the view for rendering the output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public class BannerController : Controller
{
        private readonly FlowExecutionService _flowExecutionService;

        public BannerController(FlowExecutionService flowExecutionService)
        {
            _flowExecutionService = flowExecutionService;
        }

        public ActionResult Banner()
        {

            var request = new FlowExecutionRequest()
            {
                Channel = ConfigSettings.Channel,
                ClientKey = ConfigSettings.ClientKey,
                CurrencyCode = ConfigSettings.Currency,
                Email = Sitecore.Context.User.Profile?.Email,
                FriendlyId = ConfigSettings.ExperienceId,
                Language = Sitecore.Context.Item.Language.Name,
                PointOfSale = ConfigSettings.POS
            };

            var result = _flowExecutionService.ExecuteFlow&amp;lt;FlowExecutionResult&amp;gt;(request).GetAwaiter().GetResult();
            var model = new BannerModel()
            {
                GuestEmail = result?.GuestEmail,
                GuestName = result?.GuestName
            };

            if (result != null &amp;amp;&amp;amp; ID.TryParse(result.ContentItemId, out var id))
            {
                model.ContentItem = Sitecore.Context.Database.GetItem(id);
            }

            return View("~/Views/FullStackExperience/Banner.cshtml", model);
        }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;p&gt;By creating a simple proof-of-concept like this, you will be able to integrate with much more complex interactive experiments and experiences in Sitecore Personalize. Depending on your business requirements, you can build a framework that allows specifying experience’s friendly ID and other parameters in the calling application. For instance, it can be a data source in Sitecore content tree or a configurational content item in a headless CMS. Building a foundation layer can streamline the process of adding new experiences for your marketing teams.&lt;/p&gt;




&lt;p&gt;For a complete working example, feel free to explore the code repository on &lt;a href="https://github.com/kate-orlova/sitecore-cdp" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>sitecore</category>
      <category>sitecorepersonalize</category>
      <category>webdev</category>
      <category>personalization</category>
    </item>
    <item>
      <title>Using FreeMarker template engine in Sitecore Personalize</title>
      <dc:creator>Anna Bastron</dc:creator>
      <pubDate>Mon, 28 Aug 2023 07:30:00 +0000</pubDate>
      <link>https://dev.to/annagevel/using-freemarker-template-engine-in-sitecore-personalize-5c</link>
      <guid>https://dev.to/annagevel/using-freemarker-template-engine-in-sitecore-personalize-5c</guid>
      <description>&lt;p&gt;If you have tried creating an experiment or experience within Sitecore Personalize or completed a training course, the phrase "FreeMarker syntax" might have caught your attention. In this article I will explain what is FreeMarker and how it helps to unlock the full potential of personalisation and experimentation capabilities in Sitecore Personalize.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is FreeMarker?
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://freemarker.apache.org/" rel="noopener noreferrer"&gt;FreeMarker&lt;/a&gt; is a template engine, it allows to generate text output based on templates and dynamic data. It is similar to Mustache, Handlebars, Thymeleaf and other template engines. Templates are written in the FreeMarker Template Language (FTL) that supports conditional blocks, iterations, formatting, and many other capabilities.&lt;/p&gt;

&lt;h3&gt;
  
  
  How is FreeMarker used in Sitecore Personalize?
&lt;/h3&gt;

&lt;p&gt;As a template engine, FreeMarker helps to produce output of the predefined format and make it dynamic based on the provided data input.&lt;/p&gt;

&lt;p&gt;There are two main areas where FreeMarker is used in Sitecore Personalize:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Web experiences and experiments&lt;/li&gt;
&lt;li&gt;Interactive experiences and experiments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For both of them Sitecore Personalize allows to construct API response using FreeMarker language that will be executed on the server side. Do not confuse this with Handlebars template language that is used for HTML, CSS and JavaScript sections. The key difference is that HTML, CSS and JavaScript templates will be executed on the client-side by Handlebars.js library:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F414d9xj5enclymn7pspb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F414d9xj5enclymn7pspb.png" alt="Advanced Web Experience configuration" width="800" height="297"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What data is available in API response?
&lt;/h3&gt;

&lt;p&gt;API response can reference and transform any server-side data that is available for the current guest: including guest profile data, his sessions, orders, segment memberships, and the decision model that is associated with this experience.&lt;/p&gt;

&lt;p&gt;API response data can then subsequently be used in the HTML, CSS and JavaScript sections of a web experience. Imagine wanting to access the guest’s name or email address in your JavaScript code. To achieve this, you must configure the API response to include the dynamic data so that when the experience is executed, the data is requested from the server and then passed to the front-end code.&lt;/p&gt;

&lt;p&gt;Interactive experiences work in a similar manner, but they don’t contain any HTML, CSS and JavaScript. Irrespective of the programming language you choose, your application can interact with the REST API to get dynamically produced API response and then use this data as required. I will show a simple example of such application in my next article.&lt;/p&gt;

&lt;h3&gt;
  
  
  Examples and tips
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Example 1: Basic API Response
&lt;/h4&gt;

&lt;p&gt;Please see a basic example of API response mark-up below. This API response returns guest’s email address and full name so that it can be accessed by the application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "guestEmail": "${guest.email}", 
    "guestName": "${guest.firstName} ${guest.lastName}" 
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Example 2: Conditional Sections and Segments
&lt;/h4&gt;

&lt;p&gt;Let’s add a conditional section with segments that are applicable to the current guest:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "guestEmail": "${guest.email}", 
    "guestName": "${guest.firstName} ${guest.lastName}" 
&amp;lt;#if (guest.segmentMemberships)??&amp;gt; 
    , 
    "segmentMemberships": [ 
    &amp;lt;#list guest.segmentMemberships as segment&amp;gt; 
        ${toJSON(segment)}&amp;lt;#if (segment?has_next)&amp;gt;,&amp;lt;/#if&amp;gt; 
    &amp;lt;/#list&amp;gt; 
    ] 
&amp;lt;/#if&amp;gt; 
}  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You may have noticed multiple &amp;lt;#if&amp;gt; statements in the example above, they are required to ensure that the final JSON result is valid. If there are any extra commas or non-closed brackets, Sitecore Personalize will return an error saying that the syntax is not correct.&lt;/p&gt;

&lt;h4&gt;
  
  
  Example 3: Decision Model Results
&lt;/h4&gt;

&lt;p&gt;Another common use case for API response is returning results of the linked decision model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "guestEmail": "${guest.email}", 
    "guestName": "${guest.firstName} ${guest.lastName}" 
&amp;lt;#if (decisionModelResults.decisionModelResultNodes)??&amp;gt; 
    , 
    "contentItemId": 
    &amp;lt;#list decisionModelResults.decisionModelResultNodes as resultNode&amp;gt; 
        &amp;lt;#list resultNode.outputs as output&amp;gt; 
            &amp;lt;#if (output.ContentItemID)??&amp;gt; 
                "${output.ContentItemID}" 
            &amp;lt;/#if&amp;gt; 
        &amp;lt;/#list&amp;gt; 
    &amp;lt;/#list&amp;gt; 
&amp;lt;/#if&amp;gt; 
}  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Depending on how the decision model is configured, it can return one or multiple output nodes so you should consider this when designing the API response.&lt;/p&gt;




&lt;p&gt;As you can see, FreeMarker templates in Sitecore Personalize are quite flexible and allow to tailor API response depending on your application needs and data available within Sitecore CDP. In my next blog post I will show an example of how an application can integrate with the REST API to get dynamic API responses from Sitecore Personalize. If you found this article helpful, feel free to share it with fellow developers and personalisation enthusiasts.&lt;/p&gt;

</description>
      <category>sitecore</category>
      <category>personalization</category>
      <category>freemarker</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Building decision models with Sitecore Personalize: usage of programmable nodes</title>
      <dc:creator>Anna Bastron</dc:creator>
      <pubDate>Fri, 28 Jul 2023 08:32:47 +0000</pubDate>
      <link>https://dev.to/annagevel/building-smart-decision-models-with-sitecore-personalize-the-power-of-programmable-nodes-b24</link>
      <guid>https://dev.to/annagevel/building-smart-decision-models-with-sitecore-personalize-the-power-of-programmable-nodes-b24</guid>
      <description>&lt;p&gt;In our recent research of Sitecore CDP and Personalize, my colleagues and I looked into decision models, aiming to understand how to dynamically recommend the most relevant content variants based on specific rules. We soon discovered that decision models offer much more than a simple drag-and-drop canvas! In this article I will focus on programmable nodes, an important element of the toolset available to us in Sitecore Personalize.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F12r1u63anjdcdrsj3dry.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F12r1u63anjdcdrsj3dry.png" alt="Decision Model in Sitecore Personalize" width="800" height="322"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Understanding decision models
&lt;/h3&gt;

&lt;p&gt;Decision models are the foundational element of personalised content delivery in Sitecore Personalize. They incorporate several essential components:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Programmable nodes&lt;/strong&gt;: these nodes are where the magic happens. Technical users familiar with JavaScript can utilise programmable nodes to define custom business logic for dynamic decision making.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Offers&lt;/strong&gt;: content repositories that can be used for generating recommendations. Offers are what users eventually see on your platform.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;External systems&lt;/strong&gt;: connections to external analytical models or data systems that help drive decisions within the models. A typical reason for using an external system in a decision model is to retrieve data or perform a calculation and then pass it into another decision component.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decision tables&lt;/strong&gt;: a powerful tool for crafting decision logic that allows you to map conditions and outcomes efficiently.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The importance of programmable nodes
&lt;/h3&gt;

&lt;p&gt;Programmable nodes are where developers and marketers collaborate to fine-tune the decision models. These nodes allow you to incorporate server-side JavaScript to process and analyse user behaviour data captured from Sitecore CDP across multiple sessions.&lt;/p&gt;

&lt;h4&gt;
  
  
  Why are programmable nodes so important?
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Flexibility&lt;/strong&gt;: programmable nodes offer unparalleled flexibility, enabling you to implement custom business logic tailored to your specific requirements.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Access to user data&lt;/strong&gt;: you can access guest objects and their properties from programmable nodes, including data extensions, sessions, orders, and more. This rich data allows you to make decisions and identify which content to display for this guest.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Endless possibilities&lt;/strong&gt;: with JavaScript expertise at your disposal, there are plenty of possibilities. You can easily write custom code to meet your business expectations and create unique, engaging user experiences.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Creating custom programmable nodes in Sitecore Personalize
&lt;/h3&gt;

&lt;p&gt;To illustrate the process, let's look at an example of a custom programmable node. Imagine you want to calculate the total number of "VIEW" events across all sessions for a given user and output it as an integer.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;In this code snippet, we define a function called &lt;code&gt;getNumberOfEvents&lt;/code&gt; that calculates the number of "VIEW" events within each session and returns the total count.&lt;/p&gt;

&lt;h4&gt;
  
  
  Custom programmable nodes for unique use cases
&lt;/h4&gt;

&lt;p&gt;Besides event calculations, you can build programmable nodes to cater to various other use cases. For instance, you could check the session state to determine if a user's session is still active:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Alternatively, you might want to trigger specific actions based on the last event type, such as completion of checkout:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;h3&gt;
  
  
  Empowering personalised experiences
&lt;/h3&gt;

&lt;p&gt;Programmable nodes in Sitecore Personalize provide a powerful tool to create highly personalised user experiences. By leveraging server-side JavaScript and collaborating with technical experts, marketers can configure decision models to cater for various scenarios and user preferences. So, unlock the potential of programmable nodes and deliver outstanding content tailored precisely to your users' needs! 🚀&lt;/p&gt;

&lt;p&gt;For a detailed guide on creating decision models, check out my colleague's article &lt;a href="https://www.linkedin.com/pulse/full-stack-experience-sitecore-personalized-managed-content-orlova/" rel="noopener noreferrer"&gt;here&lt;/a&gt;. Once you have that foundation, you're all set to start your journey of customisation with programmable nodes. In my next articles I will talk more about other technical features of Sitecore Personalize. Stay tuned! 😊&lt;/p&gt;

</description>
      <category>sitecore</category>
      <category>personalization</category>
    </item>
    <item>
      <title>Sitecore Federated Authentication and that annoying error IDX21323: RequireNonce is '[PII is hidden]'</title>
      <dc:creator>Anna Bastron</dc:creator>
      <pubDate>Tue, 02 May 2023 18:17:55 +0000</pubDate>
      <link>https://dev.to/annagevel/sitecore-federated-authentication-and-that-annoying-error-idx21323-requirenonce-is-pii-is-hidden-4kol</link>
      <guid>https://dev.to/annagevel/sitecore-federated-authentication-and-that-annoying-error-idx21323-requirenonce-is-pii-is-hidden-4kol</guid>
      <description>&lt;p&gt;Have you ever experienced an error that keeps appearing under different circumstances and seems to be caused by random reasons? You can fix it once or twice but it comes back in a couple of days or in another environment or for another user. This was the error IDX21323 for me, it was so annoying that after a few cases I started saying "No, not you again!" 😅&lt;/p&gt;




&lt;p&gt;I worked with Federated Authentication in different versions of Sitecore and integrated it with multiple identity platforms such as Okta, Auth0 and Azure AD B2C. It is a good foundational layer that provides a lot of core functionality out-of-the-box, but there is still plenty of room for mistakes and learnings. In this article I want to share some of these learnings, specifically about the error &lt;strong&gt;IDX21323: RequireNonce is '[PII is hidden]'&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The main symptom of it is the fact that Sitecore does not authenticate the user correctly after successful redirect back from the identity provider login page. Instead of the configured &lt;code&gt;redirectURL&lt;/code&gt;, it sends user to the &lt;code&gt;/error&lt;/code&gt; page with the following message in the URL query string:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;IDX21323: RequireNonce is '[PII is hidden]'. OpenIdConnectProtocolValidationContext.Nonce was null, OpenIdConnectProtocol.ValidatedIdToken.Payload.Nonce was not null. The nonce cannot be validated. If you don't need to check the nonce, set OpenIdConnectProtocolValidator.RequireNonce to 'false'. Note if 'nonce' is found it will be evaluated.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The message itself is not very clear but we can see more details in the &lt;code&gt;Owin.log&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;26672 08:37:29 WARN  Microsoft.Owin.Security.OpenIdConnect.OpenIdConnectAuthenticationMiddleware - The nonce cookie was not found.
26672 08:37:29 ERROR Microsoft.Owin.Security.OpenIdConnect.OpenIdConnectAuthenticationMiddleware - Exception occurred while processing message: 
Exception: Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectProtocolInvalidNonceException
Message: IDX21323: RequireNonce is '[PII is hidden]'. OpenIdConnectProtocolValidationContext.Nonce was null, OpenIdConnectProtocol.ValidatedIdToken.Payload.Nonce was not null. The nonce cannot be validated. If you don't need to check the nonce, set OpenIdConnectProtocolValidator.RequireNonce to 'false'. Note if a 'nonce' is found it will be evaluated.
Source: Microsoft.IdentityModel.Protocols.OpenIdConnect
   at Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectProtocolValidator.ValidateNonce(OpenIdConnectProtocolValidationContext validationContext) in C:\agent2\_work\56\s\src\Microsoft.IdentityModel.Protocols.OpenIdConnect\OpenIdConnectProtocolValidator.cs:line 639
   at Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectProtocolValidator.ValidateAuthenticationResponse(OpenIdConnectProtocolValidationContext validationContext) in C:\agent2\_work\56\s\src\Microsoft.IdentityModel.Protocols.OpenIdConnect\OpenIdConnectProtocolValidator.cs:line 264
   at Microsoft.Owin.Security.OpenIdConnect.OpenIdConnectAuthenticationHandler.&amp;lt;AuthenticateCoreAsync&amp;gt;d__11.MoveNext()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ultimately this error means that the Owin middleware was not able to read the nonce cookie. In fact, &lt;code&gt;OpenIdConnectProtocolValidator&lt;/code&gt; can return many other error codes but I'll focus just on one of them today. So why can this error appear?&lt;/p&gt;

&lt;h2&gt;
  
  
  Federated Authentication process
&lt;/h2&gt;

&lt;p&gt;To answer this question, we need to understand how Sitecore Federated Authentication works and what it does behind the scenes.&lt;br&gt;
Here is how the happy path should look like: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2figpvrkshdrbejw0fko.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2figpvrkshdrbejw0fko.png" alt="Sitecore Federated Authentication process steps" width="800" height="653"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1.&lt;/strong&gt; Browser requests a page that contains a login link.&lt;br&gt;
&lt;strong&gt;Step 2.&lt;/strong&gt; Sitecore finds the &lt;code&gt;IdentityProvider&lt;/code&gt; registered for the specified website name and generates login URL using the pipeline &lt;code&gt;GetSignInUrlInfo&lt;/code&gt;.&lt;br&gt;
&lt;strong&gt;Step 3.&lt;/strong&gt; User clicks the login link and browser sends a POST request to the generated login URL.&lt;br&gt;
The URL should start from &lt;code&gt;/identity/externallogin?...&lt;/code&gt;&lt;br&gt;
&lt;strong&gt;Step 4.&lt;/strong&gt; The processor &lt;code&gt;HandleLoginLink&lt;/code&gt; performs the following logic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ensures that the request method is POST&lt;/li&gt;
&lt;li&gt;includes &lt;code&gt;RedirectUri&lt;/code&gt; parameter in the Authentication properties&lt;/li&gt;
&lt;li&gt;calls OWIN  &lt;code&gt;AuthenticationManager.Challenge()&lt;/code&gt; method passing the correct authentication type
Then OWIN generates nonce, saves it in a cookie and redirects to the identity provider passing the nonce in the message.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 5.&lt;/strong&gt; Browser redirects user to the Identity Provider sign in page.&lt;br&gt;
&lt;strong&gt;Step 6.&lt;/strong&gt; IDP authenticates the user and returns a JWT token.&lt;br&gt;
&lt;strong&gt;Step 7.&lt;/strong&gt; Browser redirects back to Sitecore application with JWT token.&lt;br&gt;
&lt;strong&gt;Step 8.&lt;/strong&gt; OWIN validates that nonce returned in the JWT token is valid and matches the original nonce cookie.&lt;br&gt;
Sitecore &lt;code&gt;IdentityProviderProcessor&lt;/code&gt; validates the token, authenticates the user and redirects to the specified &lt;code&gt;RedirectUri&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If something goes wrong at any of these steps, the error IDX21323 can appear. There are some common reasons that can cause it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sitecore pipeline &lt;code&gt;GetSignInUrlInfo&lt;/code&gt; was not used for generating the sign in URL.&lt;/li&gt;
&lt;li&gt;Original domain of the website and the configured redirect URL domain are different, therefore Sitecore does not have access to the nonce cookie after redirect back from the identity provider.&lt;/li&gt;
&lt;li&gt;Cookies or session was cleared between the sign in URL generation and redirect back from the external provider.&lt;/li&gt;
&lt;li&gt;Nonce cookie is returned by the server but the browser blocks it.&lt;/li&gt;
&lt;li&gt;ASP.NET Session ID cookie is not created.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, the last time I saw this error it was intermittent and happened only on one machine and we could not reproduce it anywhere else. It turned out that the problem was caused by the disabled &lt;code&gt;xDB.Enabled&lt;/code&gt; and &lt;code&gt;xDB.Tracking.Enabled&lt;/code&gt; settings. If a request to the login page was made in a new incognito browser window, there was no ASP.NET Session ID cookie created before the redirect was made to the identity provider, therefore Sitecore could not validate that the response from the identity provider was made from the same user session. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The root cause of this behaviour is a bug in Microsoft's Owin implementation for System.Web - sometimes cookies are not saved correctly due to a conflict between Owin and System.Web libraries, as a result ASP.NET_SessionId and Owin-related cookies are not saved and it breaks authentication process. You can read more about this bug and possible workarounds on &lt;a href="https://github.com/aspnet/AspNetKatana/wiki/System.Web-response-cookie-integration-issues" rel="noopener noreferrer"&gt;this page&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Troubleshooting steps
&lt;/h2&gt;

&lt;p&gt;If you faced this error, the following steps can help to troubleshoot it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Look at requests on the browser Network tab.
It can be an IIS redirect that breaks the POST request and the problem is not even in the nonce cookie!&lt;/li&gt;
&lt;li&gt;Check that the cookies are generated and saved correctly - both ASP.NET Session ID and nonce cookie.&lt;/li&gt;
&lt;li&gt;If the website redirects back to the /error page, check the error message in query string.&lt;/li&gt;
&lt;li&gt;Review Sitecore log and Owin.log for any relevant error messages.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Bonus tip:&lt;br&gt;
If you have just started integration and don't have any components that display the logged in user details yet, create a simple test rendering that will output all information required for debugging. &lt;/p&gt;

</description>
      <category>gratitude</category>
    </item>
    <item>
      <title>Troubleshooting Sitecore content tests</title>
      <dc:creator>Anna Bastron</dc:creator>
      <pubDate>Thu, 05 Jan 2023 12:37:58 +0000</pubDate>
      <link>https://dev.to/annagevel/troubleshooting-sitecore-content-tests-1o8f</link>
      <guid>https://dev.to/annagevel/troubleshooting-sitecore-content-tests-1o8f</guid>
      <description>&lt;p&gt;Sitecore Experience Optimization is a powerful tool that allows running A/B and multi-variant tests to help content and marketing teams improve their website content and overall customer experience. However, a fully-scaled Sitecore XP has many parts that are involved in test data processing and aggregation. I met teams who had set up their A/B tests in Sitecore but struggled to see them working or couldn't get any reports.&lt;/p&gt;

&lt;p&gt;In this post I will go through the test lifetime from creation to reporting to help you identify which part of the system is not working as expected.&lt;/p&gt;




&lt;h2&gt;
  
  
  Is my A/B test working?
&lt;/h2&gt;

&lt;p&gt;The first step is to check that the test works and visitors can see different experiences. This can be done by reloading the test page a few times in an incognito browser window or by clearing cookies between attempts. Eventually you should see an alternative version of the page or component.&lt;/p&gt;

&lt;p&gt;If it does not happen and you always see the default variant, it means that Sitecore XP features are not configured properly or the Content Delivery server does not see the test definition in web database.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;The first and obvious thing to check is that these three settings are enabled in Sitecore config on the Content Delivery server: &lt;code&gt;xDB.Enabled&lt;/code&gt;, &lt;code&gt;xDB.Tracking.Enabled&lt;/code&gt; and &lt;code&gt;ContentTesting.Enabled&lt;/code&gt;.&lt;br&gt;
&lt;code&gt;ContentTesting.Enabled&lt;/code&gt; setting alone is not enough because Sitecore checks &lt;code&gt;xDB.Enabled&lt;/code&gt; and &lt;code&gt;xDB.Tracking.Enabled&lt;/code&gt; behind the scenes to determine whether content tests should run or not.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Check that you have &lt;code&gt;SC_ANALYTICS_GLOBAL&lt;/code&gt; cookie in the browser. Cookie consent modules and custom code can block this cookie if there is no consent given.&lt;br&gt;
Another possible reason for missing analytics cookie can be lack of &lt;code&gt;@Html.Sitecore().VisitorIdentification()&lt;/code&gt; tag in the website layout file.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Make sure that your page with the test and all relevant data sources are published to the Web database. If you are not sure, try republishing the page with its related items (child items are not required).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Another important element is the test definition item, it should be located in the folder &lt;code&gt;/sitecore/system/Marketing Control Panel/Test Lab&lt;/code&gt;, have the workflow state &lt;code&gt;Deployed&lt;/code&gt; and published to the Web database too.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;When in doubt, it is always good to check Content Delivery server logs, never underestimate this step!&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Is test data being saved to xDB?
&lt;/h2&gt;

&lt;p&gt;If you can see test experiences, the next step is making sure that your visits are recorded correctly in the xDB on session end. You will need access to the Shard DBs via SQL Management Studio or Azure Query Editor.&lt;/p&gt;

&lt;p&gt;Use the script below to check recent entries in the Interaction table of Shard DBs (if you have multiple shards, remember to check each of them!).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;USE [database_name] -- replace with your Shard DB name, you may need to check all your Shard DBs
GO

SELECT TOP(100) * --always limit number of results, especially if you Shard DB is large
FROM [xdb_collection].[Interactions]
WHERE ContactId = '0E3CC254-C440-0000-0000-062C9FE15922' -- replace with your contact ID if it is known
AND StartDateTime &amp;gt; '2022-01-01 12:00' -- replace with your date and time, keep the time range as small as possible

-- be careful when adding more fields to the WHERE condition because Interactions table has only 2 indexes and searching for large number of interactions can be slow
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Your interaction should contain the special event called &lt;code&gt;MVTestTriggered&lt;/code&gt; in the Events column:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzjfq3rksf6267clxojep.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzjfq3rksf6267clxojep.png" alt="Events column example" width="681" height="277"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also try checking that the contact has the facet &lt;code&gt;TestCombinations&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;USE [database_name] -- replace with your Shard DB name, you may need to check all your Shard DB
GO

SELECT *
FROM [xdb_collection].[ContactFacets]
WHERE ContactId = '0E3CC254-C440-0000-0000-062C9FE15922' -- replace with your contact ID
AND FacetKey = 'TestCombinations'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;There should be a row with data like this in the &lt;code&gt;FacetData&lt;/code&gt; column:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fklhwgcbnco9xp5eqwp07.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fklhwgcbnco9xp5eqwp07.png" alt="FacetData column example" width="712" height="89"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you don’t see this data in any of the Shard databases, the problem is between the CD server, xConnect collection service and xDB. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Make sure there are no files in the folder &lt;code&gt;/App_Data/SubmitQueue&lt;/code&gt; on CD server. If there are any, it means that CD collected tracking information but had problems pushing sessions to the xConnect collection.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Have you already checked your CD server logs? 😊 &lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If xConnect collection service returns an error or unavailable, it will be written to the CD log. I will not go into every single scenario here because most of these errors have already been discovered and solved by the community. Here are some of the articles that helped me to resolve similar issues in the past:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://support.sitecore.com/kb?id=kb_article_view&amp;amp;sysparm_article=KB0719199" rel="noopener noreferrer"&gt;Troubleshooting xConnect certificate issues&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://support.sitecore.com/kb?id=kb_article_view&amp;amp;sysparm_article=KB0977445" rel="noopener noreferrer"&gt;Troubleshooting xDB data issues&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://sivalingaamorthy.medium.com/sitecore-9-xconnect-not-working-having-error-http-error-403-16-b148bb247897" rel="noopener noreferrer"&gt;Sitecore 9 - XConnect - Not working, having error - HTTP Error 403.16 - Forbidden -Your client certificate is either not trusted or is invalid&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://robearlam.com/blog/sitecore-xconnect-xdbcollectionunavailableexception-urgh" rel="noopener noreferrer"&gt;Sitecore XConnect Xdb Collection Unavailable Exception, urgh!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://sitecorecorner.wordpress.com/2020/08/09/fix-xconnect-certificate-errors/" rel="noopener noreferrer"&gt;Fix xConnect Certificate Errors&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://darjimaulik.wordpress.com/2020/07/20/sitecore-xconnect-certificate-error-http-response-was-not-successful-forbidden/" rel="noopener noreferrer"&gt;Sitecore XConnect certificate error – HTTP response was not successful: Forbidden&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://himynameistim.com/blog/debugging-sitecore-9-analytics-issues" rel="noopener noreferrer"&gt;Debugging Sitecore 9 Analytics Issues&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Is xDB data being aggregated?
&lt;/h2&gt;

&lt;p&gt;Moving on, if you see raw data in the Interactions table correctly, the next step is finding this data in the Reporting database. The following tables will be of interest to us:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fact_MvTesting&lt;/li&gt;
&lt;li&gt;Fact_TestConversions&lt;/li&gt;
&lt;li&gt;Fact_TestPageClicks
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;USE [database_name] -- replace with your Reporting DB nam
GO

-- [Fact_MvTesting] contains aggregated number of visits, engagement value, bounces, etc. for each test variant
-- TestSetId is ID of test definition item and TestValues represent each of the test variants
SELECT *
FROM [dbo].[Fact_MvTesting]
WHERE TestSetId = 'DB071F98-C2CE-4DBB-8873-2E378F88CEB4' -- replace with your test ID

-- [Fact_TestConversions] stores aggregated number of triggered goals, associated visits and engagrement value
-- counted for each test variant and goal ID
SELECT *
FROM [dbo].[Fact_TestConversions]
WHERE TestSetId = 'DB071F98-C2CE-4DBB-8873-2E378F88CEB4' -- replace with your test ID
AND GoalId = '3430F877-4767-4B2E-A121-FDA73122FEFB' -- optionally you can filter by a specific goal ID

-- [Fact_TestPageClicks] represents aggregated number of page views
-- counted for each test variant and page ID
SELECT *
FROM [dbo].[Fact_TestPageClicks]
WHERE TestSetId = 'DB071F98-C2CE-4DBB-8873-2E378F88CEB4' -- replace with your test ID
AND ItemId = 'D8771EF4-47D5-4EDA-9F96-0785207AA052' -- optionally you can filter by a page ID
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;If there is no data in these tables or data is too old, it’s time to look at the Processing role.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Check log files on your Processing server.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ensure that &lt;code&gt;xDB.Enabled&lt;/code&gt;, &lt;code&gt;xDB.Tracking.Enabled&lt;/code&gt; and &lt;code&gt;ContentTesting.Enabled&lt;/code&gt; are set to true on the Processing server. If they are not, the Processing server will not aggregate test data correctly.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Review server resource consumption on the Processing server and databases ReferenceData, Shards and Reporting. If the server or databases are maxing out, you will see delays in aggregated data appearing in the Reporting DB.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  Is aggregated data being returned correctly?
&lt;/h2&gt;

&lt;p&gt;Starting from Sitecore 10.1 the xDB Reporting role has been combined with the Content Management role to reduce hosting costs. So if you have version 10 or lower, check your Content Management and xDB Reporting roles, especially connectivity between them. &lt;/p&gt;

&lt;p&gt;For Sitecore 10.1 and higher, it must be an issue with the Content Management role or its connection to the Reporting DB.&lt;/p&gt;



&lt;p&gt;Hopefully this article shed some light on how the A/B test data transforms and flows between different parts of Sitecore platform and which things to check when troubleshooting A/B tests.&lt;/p&gt;

&lt;p&gt;All scripts from this article are saved in the GitHub repository &lt;a href="https://github.com/geann/troubleshooting-sitecore-content-tests" rel="noopener noreferrer"&gt;https://github.com/geann/troubleshooting-sitecore-content-tests&lt;/a&gt; so you can save them for future use.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/geann" rel="noopener noreferrer"&gt;
        geann
      &lt;/a&gt; / &lt;a href="https://github.com/geann/troubleshooting-sitecore-content-tests" rel="noopener noreferrer"&gt;
        troubleshooting-sitecore-content-tests
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Scripts for troubleshooting Sitecore content tests
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Scripts for troubleshooting Sitecore content tests&lt;/h1&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/geann/troubleshooting-sitecore-content-tests/screenshots/Content%20testing%20overview.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fgeann%2Ftroubleshooting-sitecore-content-tests%2Fscreenshots%2FContent%2520testing%2520overview.png" alt="Content testing overview diagram"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This repository contains SQL scripts used in the article &lt;a href="https://www.linkedin.com/pulse/troubleshooting-sitecore-contenttests-anna-gevel/" rel="nofollow noopener noreferrer"&gt;Troubleshooting Sitecore content tests&lt;/a&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;Interactions.sql&lt;/code&gt; - this script checks raw interactions data in Shard databases with ability to filter by a date and/or contact ID.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Contact facets.sql&lt;/code&gt; - checks if a contact contains a special facet with A/B test combinations, i.e. validates the fact that this contact participated in an A/B test. Results can be filtered by contact ID.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Reporting.sql&lt;/code&gt; - checks test results in the Reporting database for specific test ID:
&lt;ul&gt;
&lt;li&gt;overall test statistics&lt;/li&gt;
&lt;li&gt;test conversions for specific goal&lt;/li&gt;
&lt;li&gt;test page clicks&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Screeenshots with example data are saved for reference to the folder &lt;code&gt;/screenshots&lt;/code&gt;.&lt;/p&gt;

&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/geann/troubleshooting-sitecore-content-tests" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


</description>
      <category>sitecore</category>
      <category>abtesting</category>
      <category>troubleshooting</category>
    </item>
  </channel>
</rss>
