Set Sitecore contact behavior profile scores using API
Functional
requirements
One of
my customers requested an API capable of changing Xdb contact profile card behavior scores
created by marketeers.
Goal was
to create profile cards in Sitecore, have an external system periodically
update the score based on their own user identifier and metrics, and finally personalize content using behavior profile scores.
Technical
requirements
- API must
be authenticated so it can only be used by authorized external parties.
- API
must be able to map an external user Id to a Sitecore Xdb
contact Id.
Solution
Updating
Profile cards scores is possible through the Sitecore.Analytics.Tracker.Current.Interaction.Profiles[XX].Score property. But this relies on user interaction,
a valid session and active tracker. None of these are available through a web service
/ API call.
As
Profile cards are created and changed over time, creating custom Xdb facets is not an option here. They require a developer
and deployments to XConnect service(s).
Instead,
I chose to set scores on the contact behavior profile facet using the XConnect client following these steps:
1. Create a Sitecore profile card and
key
2. Develop authenticated API
-
Maps
external system user Id to Sitecore contact Id.
-
Sets
behavior profile facet score directly into Xdb using
the XConnect client.
Implementation
Create
Sitecore Profile card and key
Starting
with creating a profile card in Sitecore.
1.
On the
Sitecore Launchpad, click Marketing Control Panel.
2.
In the content
tree, expand the Marketing Control Panel node and the Profiles node.
3.
Expand the
node for the profile you are interested and then click the Profile Cards
folder.
4.
On the
Home tab, in the Insert group, click Profile Card to create a new profile card.
5.
After
creating the profile card, add a profile key to it with the same name.
Implement
the Update Profilecard score API
The API
must map the external user identifier to a Sitecore contact Id. I removed this
piece of code from the example since it is very customer specific and not
relevant for the implementation. Assuming we have the Sitecore contact Id, the
API must perform a few steps:
Ø Create new instance of XConnectClient
Ø Get contact from xConnect
Ø Get ContactBehaviorProfile
facet
Ø Get the marketing profile definition by name
Ø Add or update facet profile and profile key score
Ø Submit
Ø Remove contact from session to enforce a reload
The final
implementation of the API looks like this.
using Sitecore.Analytics.Tracking;
using System.Web.Http;
using System.Web.Mvc;
using Sitecore.XConnect.Collection.Model;
using Sitecore.XConnect.Client;
using Sitecore.XConnect;
using System.Linq;
using Sitecore.Marketing.Definitions.Profiles;
using Sitecore.Analytics;
using System.Text;
using
System;
using XXX.Exceptions;
namespace XXX.ContactBehaviorProfile.Controllers
{
/// <summary>
/// This controller handles profile card
scores
/// </summary>
[Authorize]
public class ContactBehaviorProfileApiController : Controller
{
[System.Web.Mvc.HttpPost]
public ActionResult SetProfileCardScores(string id, [FromBody] Models.ProfileCardDataModel profileCardDataModel)
{
try
{
var responseMessageStringBuilder
= new StringBuilder();
using (XConnectClient
client =
Sitecore.XConnect.Client.Configuration.SitecoreXConnectClientConfiguration.GetClient())
{
// Get contact using XConnectClient
var xconnectContact
= client.Get(new IdentifiedContactReference("extranet", id), new
ContactExpandOptions(Sitecore.XConnect.Collection.Model.ContactBehaviorProfile.DefaultFacetKey));
if (xconnectContact
== null)
{
throw new ContactBehaviorProfileApiException($"Contact with
identifier ${id} could not be
found.");
}
// Iterate through profile
cards
foreach (var profileCard
in profileCardDataModel.ProfileCards)
{
// Get marketing profile
card definition from Sitecore
var profileDefinition
= (ProfileDefinition)Tracker.MarketingDefinitions.Profiles[profileCard.Name];
if (profileDefinition
== null)
{
throw new ContactBehaviorProfileApiException($"Profile card {profileCard.Name} could not be
found.");
}
// Get contact behavior
profile facet
var xConnectehaviorProfileFacet
=
xconnectContact.Facets.ContainsKey(Sitecore.XConnect.Collection.Model.ContactBehaviorProfile.DefaultFacetKey)
?
xconnectContact.Facets[Sitecore.XConnect.Collection.Model.ContactBehaviorProfile.DefaultFacetKey]
as Sitecore.XConnect.Collection.Model.ContactBehaviorProfile
: new Sitecore.XConnect.Collection.Model.ContactBehaviorProfile();
// Get the current profile
card score
var currentProfileScore
= (double)0;
if
(xConnectehaviorProfileFacet.Scores.ContainsKey(profileDefinition.Id))
{
currentProfileScore = xConnectehaviorProfileFacet.Scores[profileDefinition.Id].Score;
}
// Only continue if score
has changed
if (currentProfileScore
== profileCard.Score)
{
continue;
}
// Remove any existing
profile score
xConnectehaviorProfileFacet.Scores.Remove(profileDefinition.Id);
// Create new profile
score
var profileScore = new ProfileScore
{
ProfileDefinitionId = profileDefinition.Id,
Score
= profileCard.Score,
ScoreCount = 1
};
// Add profile key
profileScore.Values.Add(profileDefinition.Keys.First().Id, profileCard.Score);
// Add profile score to
behavior profile facet
xConnectehaviorProfileFacet.Scores.Add(profileDefinition.Id, profileScore);
// Set behavior profile
facet on contact
client.SetFacet(xconnectContact,
Sitecore.XConnect.Collection.Model.ContactBehaviorProfile.DefaultFacetKey, xConnectehaviorProfileFacet);
// Submit changes
client.Submit();
responseMessageStringBuilder.Append($"{profileCard.Name} score updated from {currentProfileScore} to {profileCard.Score}.");
}
// Remove the contact from
session.
var contactManager
= Sitecore.Configuration.Factory.CreateObject("tracking/contactManager", true) as ContactManager;
contactManager.RemoveFromSession(xconnectContact.Id.Value);
}
return Content(responseMessageStringBuilder.ToString());
}
catch (Exception exception) when (!(exception is ContactBehaviorProfileApiException))
{
throw new ContactBehaviorProfileApiException($"ContactBehaviorProfileApi failed with exception message: {exception.Message}", exception);
}
}
}
}
Results
Now that
the scores have been set. We can use the build-in personalization rule on
a behavior profile key, example:
Final thoughts
When I started with this functionality and went through the Sitecore XConnect documentation, it seemed that the request was quite easy to implement. Just get the contact, change some facets and submit it back to Xdb. However, every time a contact got identified, the new score values got reset back to their initial values.
I've spent a lot of time trying to figure out what was wrong, even hooked into the startTracking pipeline to debug the flow. At the end it seemed that I was updating the profile score, but was not updating the profile key score using the API. After updating both, everything worked as expected. The Sitecore documentation on these processes are quite poor so feel free to copy this code and/or share some feedback :)