Skip to content

An integration for synchronizing contacts and activities between Kentico Xperience and Microsoft Dynamics 365.

License

Notifications You must be signed in to change notification settings

Kentico/xperience-dynamics365-sales

Repository files navigation

Stack Overflow Kentico.Xperience.Libraries 13.0.0

Microsoft Dynamics 365 Sales integration with Kentico Xperience 13

This integration enables the synchronization of Xperience contacts and activities to a Dynamics 365 tenant, and the synchronization of updates to those contacts made by Dynamics 365 back to your Xperience website. The integration also provides custom marketing automation actions to log Dynamics 365 tasks and appointments.

Set up the environment

Import the custom module

  1. Download the latest "Kentico.Xperience.Dynamics365.Sales" export package from Releases.
  2. In the Xperience adminstration, open the Sites application.
  3. Import the downloaded package with the Import files and Import code files settings enabled.
  4. Perform the necessary steps to include the following imported folder into your project:
    • /CMSModules/Kentico.Xperience.Dynamics365.Sales

Enable the integration

  1. Open the web.config file of your Xperience administration project and navigate to the <appSettings> section.
  2. Add your Dynamics 365 Sales tenant using the Dynamics365URL application setting. This is the URL where you access Dynamics 365. If you don't have an instance, you can set up a new one using Microsoft's documentation.
<add key="Dynamics365URL" value="https://mycompany.crm4.dynamics.com" />
  1. To authenticate requests to Dynamics 365, register an application in Microsoft Azure Active Directory's App registrations tab.
  2. After registration, note the Application (client) ID and Directory (tenant) ID in the Azure Portal, and set the corresponding application settings in Xperience:
<add key="Dynamics365ClientID" value="<client ID>" />
<add key="Dynamics365TenantID" value="<tenant ID>" />
  1. Open the Certificates & secrets tab, navigate to the Client secrets tab, and create a new secret for the integration. Add this secret to Xperience via the Dynamics365Secret application setting:
<add key="Dynamics365Secret" value="<your secret>" />

Enable outgoing contact synchronization

To enable outgoing contact synchronization you must enable it in the Settings application, add specific custom fields to the Contact class to facilitate the synchronization, and map your contact fields from Xperience to desired Dynamics 365 fields.

Begin by enabling the synchronization:

  1. Open to the Settings application.
  2. Navigate to the Integration → Dynamics 365 section.
  3. Enable the Enabled checkbox under the Contact synchronization category.

Outgoing contact synchronization is managed by the Dynamics 365 contact synchronization scheduled task by default set to run every hour. The scheduled task only synchronizes contacts that have reached a minimum total score. Ensure that you have the contact scoring functionality configured correctly, then set the minimum score in the Minimum score setting. The score must be greater than zero.

This integration uses two custom contact fields to store information about contact synchronization, which must be created manually:

  1. In the Xperience administration, open the Modules application.
  2. Open the Contact management module → Classes tab → Contact class → Fields tab.
  3. Add two New fields with the following configuration:
    • Field name: ContactDynamics365RelatedID
      • Data type: Unique identifier (GUID)
      • Display field in the editing form: No
    • Field name: ContactDynamics365SynchronizedOn
      • Data type: Date and time
      • Precision: 7
      • Display field in the editing form: No

Finally, map the Xperience contact fields to the desired Dynamics 365 fields:

  1. In the Settings application, click "Edit" next to the Field mapping setting to open a new dialog window.
    • This window displays all available Xperience contact fields (including any custom fields added by your developers), and a drop-down list containing the Dynamics 365 fields.
  2. For each Xperience field, select the Dynamics 365 field to map to.
    • Fields left on "(not mapped)" will not be synchronized to Dynamics 365.
  3. Save the mapping in the Settings application after you are done.

Enable incoming contact synchronization

Xperience contacts which are linked by the outgoing synchronization can optionally be updated whenever their information is changed in Dynamics 365. To facilitate incoming synchronization, a custom .NET Web API endpoint is available in the Xperience administration at yourxperienceadmin.com/xperience-dynamics365/updatecontact. To change the Xperience contact information, you can use any approach you'd like to send a POST request to this endpoint, where the body contains the Dynamics 365 contact information in JSON format.

Note: Outgoing contact synchronization must be enabled for incoming synchronization to work.

Typically, you will want to create this request using Dynamics 365's Power Automate application, where you have several options:

  • Automated Cloud Flow: The contact data will be sent to Xperience within seconds of changing in Dynamics 365.
  • Instant Cloud Flow: The contact data is not automatically synchronized, but can be triggered manually by your Dynamics 365 users.
  • Scheduled Cloud Flow: Synchronizes Dynamics 365 contacts to Xperience in batches based on a timer.

Example

For this example, we will create an Automated Cloud Flow to synchronize the contacts to Xperience immediately after an update occurrs in Dynamics 365.

  1. Open the Power Automate application in Dynamics 365.
  2. Select the Create tab.
  3. Select Start from blank → Automated cloud flow.
  4. In the dialog box, enter a flow name and select Microsoft Dataverse's When a row is added, modified or deleted trigger.
  5. Configure the trigger to run when a contact is modified:

Trigger definition

  1. Add a new step that uses the HTTP action.
  2. Set the action parameters to send a POST request to the /xperience-dynamics365/updatecontact endpoint of your Xperience administration website.
  3. In the Body parameter, use the Dynamic content menu to get the Body object, which is the contact information from the trigger.
  4. Expand the Show advanced options menu, set the Authentication to Basic, and provide the username and password of an Xperience user with permissions to modify Xperience contacts.
    • We recommend that you don't use an administrator account, but rather a specific user with limited permissions.

HTTP request definition

Once this flow is saved and enabled, Dynamics 365 contact information will automatically be sent to your Xperience website when they are changed. If the Dynamics 365 contact's ID has been linked to an Xperience contact (via outgoing synchronization), the Xperience contact will be updated by comparing the mapped fields and updating only those whose Xperience value are different from the Dynamics 365 value.

If you'd like to change how the incoming synchronization works, you can register your own implementation of the IDynamics365EntityMapper.MapChangedColumns method. This method runs when the Dynamics 365 contact information is sent to your Xperience website, and you are provided the Dynamics 365 contact, the Xperience contact that is linked, and the field mappings. The returned Dictionary contains records where the records Key is the name of an Xperience contact field to update, and the Value is the updated value of that field.

Enable activity synchronization

Activity synchronization is not required for contact synchronization, but if you want to synchronize activities, you must enable contact synchronization.

To enable activity synchronization:

  1. Open to the Settings application.
  2. Navigate to the Integration → Dynamics 365 section.
  3. Enable the Enabled checkbox under the Activity synchronization category.

Activity synchronization is managed by the Dynamics 365 activity synchronization scheduled task by default set to run every hour.

After enabling the synchronization, you can optionally choose a Dynamics 365 user or team in the Default owner drop-down. All synchronized activities will be assigned to this user or team when created. If you leave this setting on "(not set)" synchronized activities will be left unassigned in Dynamics.

How the synchronization works

After setting up the environment, you will find two new scheduled tasks in the Scheduled tasks application of your Xperience administration. These tasks are enabled and set to run every hour by default, though you can disable them or adjust the interval as needed:

Outgoing contact synchronization

When the contact synchronization task runs, it collects all Xperience contacts that have a score equal to or greater than the Minimum score setting. The full contact data is sent to Dynamics 365 according to how you've mapped the fields, and the ID of the Dynamics 365 contact that was created is stored in a custom Xperience contact field named "ContactDynamics365RelatedID." If activity synchronization is enabled, the contact's activities are also synchronized during creation of the contact. You can also synchronize a contact to Dynamics 365 regardless of their score by adding the Import to Dynamics 365 step to a marketing automation process.

Contacts that were already synchronized in the past (e.g., the "ContactDynamics365RelatedID" fields contains a Dynamics 365 contact ID) are synchronized again during the task execution. The Xperience contact data is compared to the Dynamics 365 contact data to check whether the information was updated since the last synchronization. If no mapped fields have changed, synchronization is skipped for that contact. If some fields have changed, a partial update is made to the Dynamics 365 contact to avoid unwanted triggering of workflows (e.g., if you have a process that runs whenever a contact's email address is updated).

The Xperience contact fields that are available for mapping are automatically retrieved at run time, so any custom fields added by your developers will appear automatically. However, if you would like to adjust the retrieved fields for any reason, you can register a custom implementation of IContactFieldProvider:

[assembly: RegisterImplementation(typeof(IContactFieldProvider), typeof(CustomContactFieldProvider), Lifestyle = Lifestyle.Singleton, Priority = RegistrationPriority.Default)]
namespace MyCompany.Customizations.Dynamics365
{
    public class CustomContactFieldProvider : IContactFieldProvider
    {

Activity synchronization

This scheduled task loads all contacts that have been synchronized to Dynamics 365 and synchronizes their activities which were logged since the task's Last run time, with one exception. Since activities are synchronized immediately when a new contact is created in Dynamics 365, this task uses the "ContactDynamics365SynchronizedOn" custom field to load only the activities logged after the synchronization time.

In order for an activity to be synchronized, the activity type Code name in Xperience must match the name of the entity in Dynamics 365. For example, Dynamics 365 contains the "phonecall" entity out-of-the-box, which you can see by opening the Power apps application and navigating to Data → Tables:

Dynamics 365 entities

To synchronize "phone call" activities from from Xperience to Dynamics 365, you would create a custom activity type whose Code name matches the Dynamics 365 entity name:

Phone call activity

You can customize this behavior by developing your own implementation of IDynamicsEntityMapper:

[assembly: RegisterImplementation(typeof(IDynamics365EntityMapper), typeof(CustomEntityMapper), Lifestyle = Lifestyle.Singleton, Priority = RegistrationPriority.Default)]
namespace MyCompany.Customizations.Dynamics365.Sales
{
    /// <summary>
    /// A custom Entity mapper for Dynamics 365. 
    /// </summary>
    public class CustomEntityMapper : IDynamics365EntityMapper
    {

Note When registering custom implementations of the integration's interfaces, make sure to include the original code found in /CMSModules/Kentico.Xperience.Dynamics365.Sales/Services/Implementations/.

The MapActivityType method is called while synchronizing activities to determine the name of the Dynamics 365 entity to create, using the passed activityType parameter. For example, if you want to create "phonecall" activities in Dynamics 365, but the code name of the activity in Xperience is "phone," your implementation would look something like this:

public string MapActivityType(string activityType)
{
   if (activityType.Equals("phone", StringComparison.OrdinalIgnoreCase))
   {
         return "phonecall";
   }

   return activityType;
}

Once the Xperience activity is configured, the activity data must be mapped to the Dynamics 365 entity by your developers as there is no way for the integration to know where the information should be stored. Implement the MapActivity method which is called during activity synchronization to map Xperience activity data to an anonymous JObject, which is sent to Dynamics 365. In this method, you are provided with the name of the Entity being created (e.g., "mycustomactivity"), the ID of the Dynamics 365 contact that performed the activity, and the relatedData of the activity. The related data is an CMS.Activities.ActivityInfo object when mapping standard Xperience activities.

Example: synchronizing the "Page visit" activity

The Xperience "Page visit" activity is not synchronized by default, as there is no Dynamics 365 activity with the name "pagevisit." However, using the instructions in the above section, you can easily track what pages your contacts visited in Dynamics 365.

  1. Open the Power Apps application in Dynamics 365.
  2. Expand the Data tab and click Tables.
  3. Click New table.
  4. Set the desired table name and properties. Ensure that the Table type is "Activity table:"

Page visit creation

  1. Once the creation is finished, view the Columns of the table and click New column.
  2. Set the properties of the new column. In this case, we are creating a column to store the visited URL:

Page URL creation

  1. Note the name of the new column (e.g. "cr0a3_pageurl") and the table name (e.g. "cr0a3_pagevisit").

  2. In your Xperience CMS project, create a custom IDynamics365EntityMapper as described in the Activity synchronization section. You can copy the code from DefaultDynamics365EntityMapper to get started.

  3. Use the MapActivityType method to translate the Xperience activity name "pagevisit" into your custom Dynamics 365 activity name noted in step 7:

public string MapActivityType(string activityType)
{
   if (activityType == PredefinedActivityType.PAGE_VISIT)
   {
         return "cr0a3_pagevisit";
   }

   return activityType;
}
  1. Use the MapActivity method to add the desired data to the columns you added to the table in step 7:
public JObject MapActivity(string entityName, string dynamicsId, object relatedData)
{
   var entity = new JObject();
   MapCommonActivityProperties(dynamicsId, entity, relatedData);

   switch (entityName)
   {
         case "cr0a3_pagevisit":
            var activity = relatedData as ActivityInfo;
            entity.Add("cr0a3_pageurl", activity.ActivityURL);
            entity.Add("subject", $"Contact visited {activity.ActivityURL}");
            break;
         //...
   }

   DecodeValues(entity);

   return entity;
}
  1. Build the project

With this customization, when your Xperience contacts visit pages on your website, the Xperience "Page visit" activity will be automatically synchronized by the "Dynamics 365 activity synchronization" scheduled task and can be viewed directly in the Dynamics 365 contact's timeline.

Creating Tasks and Appointments

This integration contains two custom Marketing automation actions which will create Dynamics 365 activities when they execute:

  • Create Dynamics 365 task
  • Create Dynamics 365 appointment

With the Create Dynamics 365 task action, you can create a task for your Dynamics 365 users to complete. For example, if you want to send flowers to everyone who submits a form on your site, the process could look like this:

Task automation process

The task will be assigned to the user or team specified by the Default owner setting, or unassigned if not set. You can also create an appointment in Dynamics 365 using the Create Dynamics 365 appointment action. For example, if a partner submits a form indicating they'd like to have a meeting to discuss a new opportunity, the process might look like this:

Appointment automation process

The appointment can contain required and optional attendees that you choose from your Dynamics 365 users. The appointment will always include the contact that is currently in the automation process.

Automating Microsoft Teams messages

Using the magic of Power Automate, your team can be automatically notified of new Dynamics 365 contacts or activities when they are created. For example, if a visitor on your site fills out a "Contact Us" form requesting more information about your products, a new post can be created in your Teams "Sales" channel for your Sales team to follow up immediately.

Teams sample message

To accomplish this, first you need to synchronize the Kentico Xperience "Form submission" activity to Dynamics 365. You can follow this example to create a new activity type in Dynamics 365 for form submissions. When creating custom columns in the table, add one called "Form name" to hold the code name of the Xperience form. The mapping logic in your MapActivity implementation could look something like this:

case "cr0a3_formsubmission":
   var activity = relatedData as ActivityInfo;
   var formInfo = BizFormInfo.Provider.Get(activity.ActivityItemID);
   var dataClass = DataClassInfoProvider.GetDataClassInfo(formInfo.FormClassID);
   var formDetail = BizFormItemProvider.GetItem(activity.ActivityItemDetailID, dataClass.ClassName);
   var activityBody = new StringBuilder();
   foreach (var column in formDetail.ColumnNames)
   {
      var submittedData = formDetail.GetStringValue(column, String.Empty);
      if (!String.IsNullOrEmpty(submittedData))
      {
            activityBody.Append($"{column}: {submittedData}\r\n");
      }
   }

   entity.Add("subject", $"Contact submitted form '{formInfo.FormDisplayName}'");
   entity.Add("cr0a3_formname", formInfo.FormName);
   entity.Add("description", activityBody.ToString());
   break;

With your custom code in place, you should start to see form submission activities automatically synchronized from Xperience to Dynamics 365. Now, it's time to send a Teams message when that happens!

  1. Open the Power Automate application in Dynamics 365.
  2. Click the Create tab.
  3. Click Start from blank → Automated cloud flow.
  4. In the dialog box, create a flow name and select Microsoft Dataverse's "When a row is added, modified or deleted" trigger.
  5. Configure the trigger to run when a form submission is added.
  6. Add a Condition control after the trigger. This control will check the custom "Form name" column added to the activity, which contains the Xperience form name. Since we want to only send Teams messages for the "Contact Us" form, copy the code name from Xperience's Forms application → (edit form) → General tab → Form code name field.

After adding the Condition control, you will see two paths you can add more actions to:

Teams flow condition control

  1. In the "If yes" path, add the Get a row by ID action to find the contact that submitted the form:

Teams flow get contact

  1. After that, add a Microsoft Teams Post message in chat or channel action. Here, you can construct the body of the message using dynamic data from the activity (trigger) and the contact (from the previous step). In the sample code posted above, we set the form data to the Dynamics 365 activity's description field, so we can include that in the body.

If you want to provide a direct link to the Dynamics 365 contact, you can use the approach described in this article. In this example, we used the function uriHost(body('Get_a_row_by_ID')?['@odata.id']) to get the URL of the Dynamics 365 tenant. To replace the line breaks in the activity description for better Teams formatting, you can use uriComponentToString(replace(uriComponent(triggerBody()['description']), '%0A', '<br/>'))

The finished message looks something like this:

Teams flow message body

  1. Finally, add a Terminate control to the "If no" path to end the flow if the form name doesn't match the "Contact Us" form.

Once you save and enable the flow, you've successfully automated the synchronizing of Xperience form submissions and their data to Dynamics 365, and Teams messages containing the form data and a link to the contact!

Contributing

If you'd like to contribute to this project, please see CONTRIBUTING.md for more details on how to start. When you've made your code changes and are ready to submit a pull request, you will need to export the Dynamics 365 custom module and code files from the Xperience Sites application:

  1. Open the Modules application and edit the Dynamics 365 module.
  2. On the General tab, increment the Module version using SemVer standards. E.g., increment the patch version for backwards compatible bug fixes, or the minor version for new backwards compatible functionality.
  3. Clear all settings related to the integration. This can be done in Settings → Integration → Dynamics 365, or in the database using a query like below. Use caution when querying the database directly and ensure that you have a backup and you modify the query as needed!
UPDATE CMS_SettingsKey SET KeyValue = NULL WHERE KeyName LIKE 'dynamics365%'
  1. Open the Sites application and click the Export button.
  2. Name the export file using the format Kentico.Xperience.Dynamics365.Sales.X.Y.Z.zip, where "X.Y.Z" is the version set in step #2.
  3. Select the "(no site, only global objects)" and "Do not preselect any objects" options and click Next.
  4. Uncheck All objects → Export tasks.
  5. In Global objects → On-line marketing → Automation actions, check the following options (and any you've added):
  • Create Dynamics 365 appointment
  • Create Dynamics 365 task
  • Import to Dynamics 365
  1. In Global objects → Development → Form controls, check the following options, check the following options (and any you've added):
  • Dynamics 365 option set selector
  • Dynamics 365 user selector
  1. In Global objects → Development → Modules, check the "Dynamics 365" option.
  2. In Global objects → Configuration → Scheduled tasks, check the following options, check the following options (and any you've added):
  • Dynamics 365 activity synchronization
  • Dynamics 365 contact synchronization
  1. In Global objects → Configuration → Settings keys, check the following options, check the following options (and any you've added). They should be located on the final page of the list:
  • Default owner (Dynamics365DefaultOwner)
  • Enabled (Dynamics365ContactSyncEnabled)
  • Enabled (Dynamics365ActivitySyncEnabled)
  • Field mapping (Dynamics365ContactMapping)
  • Minimum score (Dynamics365MinScore)
  1. Include any other objects from other categories that you've added. You can see our documentation for more information about exporting objects.
  2. When you're finished selecting export objects, click Next and the export package will be created in the appropriate location and included in the repository.