This article describes how to set up Salesforce synchronization in your knowledgebase. Synchronization between Salesforce and allows you to integrate and update your data in real time between both systems. If you already use Salesforce to manage marketing, sales, service, or other content, it's much easier to sync content between both systems than to manually re-enter existing data in . Similarly, if you already have data in and want to transfer that data to Salesforce, syncing the data saves both time and effort, and it ensures data in both systems is kept up to date.
All Salesforce tables are supported in , including both standard tables and custom tables. For a given table, you can choose to sync some, all, or none of the fields.
|
Start by launching the Agiloft managed package in Salesforce and connecting the two systems. Note that the managed package is an add-on, so you must work with to purchase the add-on and access the link to set up Salesforce this way. The managed package is compatible with Professional, Enterprise, and Unlimited editions of Salesforce.
The Salesforce managed package is designed to sync Salesforce Opportunity records with Contract records, including Opportunity line items and Products. It doesn't include other objects out-of-the-box, nor does it offer multi-to-multi table mappings. To set up Salesforce without the managed package, skip to the Configuring Advanced Settings section and create a new sync configuration manually. If you want to use multi-to-multi table mappings, you will need to create two one-way sync configurations, each with multi-to-multi table mappings enabled, with one syncing data from to Salesforce and the other syncing from Salesforce to . |
To install the managed package:
Obtain the direct link and password to install the managed package from your contact.
Agiloft personnel can find this information at Salesforce Managed Package. |
Log in with your Salesforce developer account.
At the bottom, you can click Add Mapping to open additional rows for you to map fields, or click Reset Mappings to return to the default configuration.
At this point, you can only configure mappings for the primary contract table. After you finish setting up sync for the primary contract table, you can map additional tables by Configuring Advanced Settings. If you included a mapping between a Salesforce picklist and Choice field, make sure to complete Picklists to Choice Lists. |
With the managed package configured, the Agiloft CLM section of Salesforce includes a Sync to Agiloft button to push data to . This is visible for all users selected in step 4 above, when the package was installed.
When you use the managed package setup, this is the usual user workflow:
|
After you've connected your KB with Salesforce, you might want to sync additional objects and tables beyond contracts. You can do so by adding more entities to the sync configuration on the side.
If you didn't use the managed package because you needed to sync with different settings, use these steps to create a sync configuration from scratch. In this case, you need to first obtain your Salesforce security token using the developer account. In Salesforce, click your user icon in the top right and go to Settings > My Personal Information > Reset Security Token. This sends the security token to your account's email address. For more information, see Reset Your Security Token. |
To access advanced settings:
In your knowledgebase, go to Setup > Sync > External Sync.
If you connected to Salesforce already, the ESA Settings tab is also already configured, so you can skip to the next step. If you're creating a new sync configuration, select a login option:
OAuth 2.0: For this method, you need a Redirect URI, Key, and Secret value. To get them, follow these steps:
login
. In versions prior to release 23, you must also contact Support and ask them to update the values for these variables directly in the database. Finally, clear the KB cache in the admin console (Debugging > Cache > Clear Cache), or contact Support and ask them to do it if you don't have admin console access.First, use the wizard text to help you construct the Redirect URI appropriately. It should follow the https://<server>:443/gui2/sf/oauth/login
format, with <server> being the specific domain of your KB.
In another browser window or tab, log in to Salesforce and go to the Setup menu.
Go to the App Manager and click New Connected App.
Complete the required Connected App Name, API Name, and Contact Email fields.
Select the Enable OAuth Settings checkbox.
Copy the Redirect URI you entered in step 3a, then return to Salesforce and paste that value into the Callback URL field.
Then, click Verify Credentials to make sure the authorization is working.
Login/Password: For a Production environment, enter https://login.salesforce.com/services/Soap/u/33.0 in the Salesforce WSDL Endpoint field. For a Salesforce sandbox, use https://test.salesforce.com/services/Soap/u/33.0. as the WSDL Endpoint. If you receive an error, follow the SOAP API Developer Guide to identify the current endpoint. Then, enter your Salesforce account email login. In the Password field, use the following format: salesforcepasswordSECURITYTOKEN. For example, SfP4s5w[]rdSECURITYTOKEN.
On the Mapping tab, map the tables and fields with the external Salesforce system. For each mapping:
Locate the table in the list and then select the corresponding Salesforce table from the drop-down menu.
Click Map. The Field Mapping wizard opens. On the Field Mapping tab:
In the Record Modification Timestamp Field, select a field that provides the timestamp in each system to indicate the latest version of the record. If you set the table to Synchronize in the System Update Type in the next step, the timestamp field is compared between systems to determine which record's values are synced to the other system.
For each Salesforce field you want to sync, select the corresponding field from the drop-down. Then, choose whether the field value is updated in , Salesforce, or both systems. Update options are disabled if the sync direction does not permit them. For fields with multiple choice values, complete the dialog box that opens to map individual choice fields.
Use this tab to map links to single fields, but not complete linked sets. Linked sets should instead be mapped in step 6 below, on the Relation Mapping tab. |
Use the Identifying column to select fields to identify matching records between the systems. If you want to match records only when all the Identifying fields match, select the "Use strict match for identification" option. Otherwise, the system first attempts to identify matches by all fields, and if no match is found, it then narrows down the Identifying fields one at a time until a match is found.
The fields you choose as Identifying also depend on the direction of sync. To sync from Salesforce to only, select the Salesforce Record ID field as Identifying, and map it to a Text field in that will store the value. To sync from to Salesforce only, select the ID field as Identifying, and map it to a number field in Salesforce that will store the value. For two-way sync, choose a field that is unique, but has the same value in both systems, always; for example, an email address. You can't use the ID fields for this purpose because the Salesforce ID is different than the ID for the same record. |
On the Relation Mapping tab, you need to explicitly match any linked field relationships across mapped tables that you did not select on the Mapping tab. In general, it's best to use the Mapping tab for links to single fields, or linked sets where only one of the fields in the set will be synced. For linked sets, use Relation Mapping to connect the complete set with the appropriate Salesforce entity. Note that you can't map relationships where the field in one system is required, but the field in the other isn't.
Set any linked field relations between mapped tables. Linked relationships between Salesforce tables must be preserved with corresponding links in or you cannot save the sync configuration. See the second bullet of the Additional Notes section below for more information.
On the Running tab, choose whether synchronization is initiated manually, via actions and rules, or via Salesforce. For more discussion of these options, see Automating Synchronization.
To run a sync manually, go to a table's action bar and select Actions > Sync > Run [Configuration Name]. In the window that appears, click Synchronize now to run the sync. You can watch its progress, and if needed, click the hyperlinks to review the log file or the affected records. |
Click Finish.
If you are mapping a Salesforce picklist or multi-picklist to as a choice or multi-choice field, you need to perform some additional configuration after uploading the field mapping. This configuration involves choosing the values that will appear in the list.
When you choose to install the package for admin users, all users, or a list of specific users, Salesforce grants a set of permissions based on that selection. If you add more users in the future, you must manually grant those permissions to those users in Salesforce if they need to use the integration with .
In Salesforce:
Before attempting to sync with Salesforce, check the configuration of the fields you mapped:
Make sure linked fields are mapped only once, either on the Mapping tab or the Relation Mapping tab. In general, it's best to use the Mapping tab for links to single fields, or linked sets where only one of the fields in the set will be synced. For linked sets, use Relation Mapping to connect the complete set with the appropriate Salesforce entity.
For each linked field being synced, edit the field, go to the Mapping tab, and select "Allow entries not in source table."
In Salesforce, the Contracts to Accounts relationship is required. For any contract in the Contracts table, you must create a link to the Accounts table specifying the relevant account. In , this relationship is instead a link from the Contracts table to the Companies table, but the link is not required for a given contract. If you're syncing the Contracts table, you need to go to the Contracts table in and configure the linked set to the Company table to be required. You can do this on the Options tab of the Linked Field wizard by changing the "Require the user to choose record(s) to be imported?" option to Yes. |
These are necessary for the sync to save values from Salesforce that differ from values. The following fields are common changes in the out-of-the-box knowledgebase:
If you need to copy or sync your KB to another KB, such as a test KB you use that is kept separate from your production KB, you need to change all sync configurations. This includes both hotlinks and external IDs, in both and Salesforce, because the sync is completely separate from the sync you configured in any other KB. |
If you only need to synchronize and Salesforce occasionally, you can manually sync them without additional setup. If you need to keep the systems in sync on a daily basis, or in real time, you can automate your Salesforce sync using the steps below.
Use the following steps to set up a time-based rule to automate the sync process. As rules can be set to run as often as every 5 or 10 minutes and Real-Time Automation is difficult to configure, this is often the best choice.
The sync operates for all tables mapped in the sync configuration, regardless of which table contains the rule. If you need to sync some tables separately, create a separate Salesforce sync configuration for each table and use them in different Sync actions and rules. However, be careful with multiple Salesforce syncs so that you don't create conflicts with existing mappings.
You can automatically push updates between and Salesforce in real time, so that when someone makes a change to a record in one system, that change is automatically synced to the other as soon as the change is saved. Note that the table mappings in 's sync configuration must have System Update Type set to Synchronize in order for real-time automation to use those mappings.
You can sync individual records with Salesforce by configuring a Sync action in with the "Sync a single record only" checkbox selected. Make sure this action is used in a context where records are selected, such as one of the following:
If you configure a Sync action to sync a single record only, and you run that action in a context where records are not selected, no records are synced by the action. If you need a Salesforce Sync action for a time-based rule or another non-record-specific context, you must create a separate Sync action to use. |
With real-time sync, you should also confirm your settings in the main sync record:
On the Salesforce side, you can use these URL parameters to sync a particular record: &SeanceValidator.Kick.Logged.Users=false&explicittables=SF_TABLE_NAME&explicitids=SF_RECORD_ID
Follow the steps below to initiate a sync with when a record is changed in Salesforce, where only that changed record is synced. The table mappings used must have System Update Type set to Synchronize in order for them to be used in such a sync.
example.agiloft.com
, rather than the server hostname, such as ps108.agiloft.com
. https://[server_name]/gui2/login.jsp?keyID=0&user=[user_name]&passwd=[passwd]&KB=[KB]&action=sync&externalSystemID=[externalSystemID]
With this information from , you can proceed to Salesforce.
For the class, enter the following code, customizing it with your encrypted hotlink URL (in put_your_generated_hotlink_here
) and your complete set of synchronized tables (listed after or instead of Account.sObjectType.getDescribe().getKeyPrefix()
)that you mapped in the sync configuration:
public class AgiloftCallSyncUrl { /* Put encoded HotLink below */ private static String getBaseSyncUrl() { return 'put_your_generated_hotlink_here'; } public static boolean isSyncAllowed(ID idRec) { /* You can add other Tables to check if they are allowed to launch Sync process */ Set<String> allowedTables = new Set<String>{ Account.sObjectType.getDescribe().getKeyPrefix(), Asset.sObjectType.getDescribe().getKeyPrefix() }; String strObjPrefix = String.valueOf(idRec).substring(0, 3); return allowedTables.contains(strObjPrefix); } @future (callout=true) private static void callSyncURL(String url) { Http http = new Http(); HttpRequest request = new HttpRequest(); System.debug('Try to get for URL: ' + url); request.setEndpoint(url); request.setMethod('POST'); HttpResponse response = http.send(request); String cookies = response.getHeader('Set-Cookie'); System.debug('The status of the result: ' + response.getStatusCode() + ' ' + response.getStatus()+',cookies:'+cookies); System.debug(response.getBody()); String redirectionUrl = response.getHeader('Location'); System.debug('The status of the result: ' + response.getStatusCode() + ' ' + response.getStatus() + ',body:' + response.getBody() + ',redirectionUrl:' + redirectionUrl); if(response.getStatusCode() >=300 && response.getStatusCode() <= 307 && response.getStatusCode() != 306 && redirectionUrl != null) { request = new HttpRequest(); request.setEndpoint(redirectionUrl); request.setHeader('Cookie', cookies); request.setMethod('GET'); response = http.send(request); System.debug('The status of the redirect result: ' + response.getStatusCode() + ' ' + response.getStatus() + ',body:' + response.getBody()); } } public static void callSyncOnListId(List<ID> idsToSyncList) { Map<String, Set<String>> mapIds = new Map<String, Set<String>>(); for(ID idRec: idsToSyncList) { System.debug('idRec:'+idRec); String sObjName = idRec.getSObjectType().getDescribe().getName(); if (isSyncAllowed(idRec)) { Set<String> ids = mapIds.get(sObjName); if (ids == null) { ids = new Set<String>(); mapIds.put(sObjName, ids); } ids.add(String.valueOf(idRec)); } } if (mapIds.keySet().size() > 0) { String tables = ''; List<String> idsList = new List<String>(); for (String table : mapIds.keySet()) { if (tables.length() > 0) { tables=tables+','; } tables=tables+table; String ids = ''; for (String id: mapIds.get(table)) { if (ids.length() > 0) { ids=ids+','; } ids=ids+id; } idsList.add(ids); } String syncUrl = getBaseSyncUrl() + '&SeanceValidator.Kick.Logged.Users=false&explicittables='+tables; for (String ids: idsList) { syncUrl=syncUrl+'&explicitids='+ids; } System.debug('Try to start sync ' + syncUrl); callSyncURL(syncUrl); System.debug('Successfully called sync '); } } } |
In the console, enter the code for the appropriate table from the code blocks listed below. To use more than one of the tables below, repeat these steps to create an additional trigger for each additional table. When you add the code, make sure the trigger syntax matches the trigger name you chose.
trigger Account_sync_trg on Account (after insert, after update) { System.debug('Trigger Account_sync_trg start'); List<ID> idsListToSync = new List<ID>(); for (Account rec : Trigger.new) { idsListToSync.add(rec.Id); } AgiloftCallSyncUrl.callSyncOnListId(idsListToSync); System.debug('Trigger Account_sync_trg end'); } |
trigger Attachment_sync_trg on Attachment (after insert, before delete, after update) { System.debug('Trigger Attachment_sync_trg start'); Attachment[] recs; if(Trigger.isInsert || Trigger.isUpdate) { recs = Trigger.new; } if(Trigger.isDelete) { recs = Trigger.old; } List<ID> idsListToSync = new List<ID>(); for (Attachment rec : recs) { Attachment parentDetails = [SELECT ParentId FROM Attachment Where Id = :rec.Id]; idsListToSync.add(parentDetails.ParentId); } AgiloftCallSyncUrl.callSyncOnListId(idsListToSync); System.debug('Trigger Attachment_sync_trg end'); } |
trigger ContentDocument_sync_trg on ContentDocument (after insert, before delete, after update) { System.debug('Trigger ContentDocument_sync_trg start'); ContentDocument[] recs; if(Trigger.isInsert || Trigger.isUpdate) { recs = Trigger.new; } if(Trigger.isDelete) { recs = Trigger.old; } List<ID> idsListToSync = new List<ID>(); for (ContentDocument rec : recs) { for(ContentDocumentLink cdl : [SELECT LinkedEntityId FROM ContentDocumentLink WHERE ContentDocumentId = :rec.Id]){ idsListToSync.add(cdl.LinkedEntityId); } } AgiloftCallSyncUrl.callSyncOnListId(idsListToSync); System.debug('Trigger ContentDocument_sync_trg end'); } |
trigger ContentDocumentLink_sync_trg on ContentDocumentLink (after insert, before delete, after update) { System.debug('Trigger ContentDocumentLink_sync_trg start'); ContentDocumentLink[] recArr; if(Trigger.isInsert || Trigger.isUpdate) { recArr = Trigger.new; } if(Trigger.isDelete) { recArr = Trigger.old; } for (ContentDocumentLink rec : recArr) { if (AgiloftCallSyncUrl.isSyncAllowed(rec.LinkedEntityId)) { DescribeSObjectResult describeResult = rec.LinkedEntityId.getSObjectType().getDescribe(); Map<String, Schema.SObjectField> fieldMap = describeResult.fields.getMap(); // Get first updatable field String fieldToUpdate = null; for (String fieldName : fieldMap.keySet()) { if (fieldMap.get(fieldName).getDescribe().isUpdateable()) { fieldToUpdate = fieldName; break; } } if (fieldToUpdate != null) { Sobject parentTable = Database.query('SELECT Id, '+fieldToUpdate+' FROM ' + describeResult.getName() + ' where Id=\''+rec.LinkedEntityId+'\''); parentTable.put(fieldToUpdate, parentTable.get(fieldToUpdate)); update parentTable; } } } System.debug('Trigger ContentDocumentLink_sync_trg end'); } |
trigger FeedItem_sync_trg on FeedItem (before delete) { System.debug('Trigger FeedItem_sync_trg start'); FeedItem[] recs; if(Trigger.isInsert || Trigger.isUpdate) { recs = Trigger.new; } if(Trigger.isDelete) { recs = Trigger.old; } List<ID> idsListToSync = new List<ID>(); for (FeedItem rec : recs) { idsListToSync.add(rec.ParentId); } AgiloftCallSyncUrl.callSyncOnListId(idsListToSync); System.debug('Trigger FeedItem_sync_trg end'); } |
File with Versioning Fields are synchronized with Salesforce Attachments automatically, but if you intend to synchronize with Salesforce Files and need to make sure that changes are automatically reflected in , you need to enter code in the Salesforce developer account. The code automatically updates the Last Modified field of the containing record whenever the File is changed in the Salesforce account, which ensures the latest version of the File is synced.
If you already set up Real-Time Automation for the ContentDocumentLink table in Salesforce, you are already syncing with Salesforce Files and you don't need to complete this setup.
The sync creates a new version of the file whenever a change is made, so make sure the field has Enable Versioning set to Yes on the Options tab of the Field wizard. Otherwise, changes from Salesforce to are only compared to the first sync. |
To set up Files sync:
For the class, enter the following code, customizing it to use your set of synchronized tables:
public class AgiloftCallSyncUrl { public static boolean isSyncAllowed(ID idRec) { /* You can add another Tables to check if they are allowed to launch Sync process */ Set<String> allowedTables = new Set<String>{ Account.sObjectType.getDescribe().getKeyPrefix(), Asset.sObjectType.getDescribe().getKeyPrefix() }; String strObjPrefix = String.valueOf(idRec).substring(0, 3); return allowedTables.contains(strObjPrefix); } } |
In the console, enter the following code, customizing it to use your set of synchronized tables:
trigger ContentDocumentLink_sync_trg on ContentDocumentLink (after insert, before delete, after update) { System.debug('Trigger ContentDocumentLink_sync_trg start'); ContentDocumentLink[] recArr; if(Trigger.isInsert || Trigger.isUpdate) { recArr = Trigger.new; } if(Trigger.isDelete) { recArr = Trigger.old; } for (ContentDocumentLink rec : recArr) { if (AgiloftCallSyncUrl.isSyncAllowed(rec.LinkedEntityId)) { DescribeSObjectResult describeResult = rec.LinkedEntityId.getSObjectType().getDescribe(); Map<String, Schema.SObjectField> fieldMap = describeResult.fields.getMap(); // Get first updatable field String fieldToUpdate = null; for (String fieldName : fieldMap.keySet()) { if (fieldMap.get(fieldName).getDescribe().isUpdateable()) { fieldToUpdate = fieldName; break; } } if (fieldToUpdate != null) { Sobject parentTable = Database.query('SELECT Id, '+fieldToUpdate+' FROM ' + describeResult.getName() + ' where Id=\''+rec.LinkedEntityId+'\''); parentTable.put(fieldToUpdate, parentTable.get(fieldToUpdate)); update parentTable; } } } System.debug('Trigger ContentDocumentLink_sync_trg end'); } |
When you configure integration with a new Salesforce object, you need to make sure your Salesforce account has Create permission for that object.
If you need to configure your Salesforce sync without using the managed package, you must first make sure your systems have the necessary structure to allow syncing between them. After you complete this section, return to Configuring Advanced Settings to begin setup.
Review the tables and fields in Salesforce that you want to sync. To do so, click the dots in the top left of the Sales Console to open the App Launcher. In the App Launcher, select the app that contains your data or most closely suits your integration purpose. In Salesforce, apps are containers that hold the tables you can map to Agiloft with your integration. From the app page, you can then select a table and create a test record to see fields in that table.
Make sure all tables and fields you want to sync with Salesforce have been created and configured appropriately. If you want to sync the Contracts table specifically, you need to sync both the Contracts and Companies tables because they share so much information. For best results, follow these steps to prepare the Companies and Contracts tables:
Now, prepare the Contracts table:
As you complete the steps in the Configuring Advanced Settings section, make sure to map both the Account ID and the Account Name from Salesforce to fields in the Companies table. Also make sure the Account ID is mapped in the Relation Mapping settings. When you test your synchronization, check that the Salesforce Account ID is populated in both tables.
If you set up your integration using the managed package, and the Sync to Agiloft button in Salesforce does not sync new contracts to :
Related articles |