Page tree
Skip to end of metadata
Go to start of metadata

Salesforce Integration

This article describes how to set up Salesforce synchronization in your Agiloft knowledgebase. Synchronization between Salesforce and  Agiloft 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  Agiloft. Similarly, if you already have data in Agiloft 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  Agiloft, including both standard tables and custom tables. For a given table, you can choose to sync some, all, or none of the fields.

Prerequisites

  • Have a working email account ready to use as a login and to receive notifications.
  • Create a Salesforce developer account using your email account.
  • Obtain your Salesforce security token after creating a Salesforce account. In Salesforce, click your user icon in the top right and go to Settings > My Personal Information > Reset Security Token. The security token is sent to your account's email address. For more information, see Reset Your Security Token.
  • Ensure that all fields you want to sync with Salesforce have been added to the knowledgebase.

Before proceeding with the setup, you might also want to review the tables and fields in Salesforce that already contain or will contain your data. 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.

Preparing to Sync the Contracts Table

To sync the Contracts table from  Agiloft to Salesforce, you need to sync both the Contracts and Companies tables because they share so much information. Before you begin the steps in the Configuring Synchronization section below, take these steps to prepare the Companies table:

  1. Go to Setup Companies and go to the Fields tab.
  2. Create a new Text or Short Text field.
  3. Name the field Salesforce Account ID, or something similar. This field will receive the account ID from Salesforce.
  4. Go to the Options tab and make the field required
  5. If you want to prevent companies from sharing a Salesforce ID, make the field unique.
  6. Click Finish.

Now, prepare the Contracts table:

  1. Go to Setup Contracts and go to the Fields tab.
  2. Edit the linked field set from the Companies table and add the new Salesforce Account ID field to the set.
  3. As part of a linked set, you can't require this specific field be set. Instead, you can do one or both of the following to enforce or encourage setting a value in this field:
    • Set up a Validate Action to prevent saving when the Salesforce Account ID field is empty.
    • Edit the linked field set, go to the Mapping tab, select "Allow entries not in source table," and then make the Salesforce Account ID field required. You might also want to create a rule that triggers when the Salesforce Account ID changes, and then executes a Linked Record Action to copy the new Salesforce account number.

As you complete the steps in the Configuring Synchronization 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.

Configuring Synchronization

Follow these steps to configure synchronization between  Agiloft and Salesforce.

  1. In your Agiloft knowledgebase, go to  Setup > Sync > New > External Sync.

  2. On the General tab:
    1. Set External System Type to Salesforce. Leave the default Third-party ESA (HTTP) option selected.
    2. Set up Directions, Related tables, and Conflicts according to your system requirements. These settings are treated as the default for this sync, but you can configure individual tables to have different directions on the Mapping tab in step 4.

    3. If desired, set up personal synchronization options or API calls.

    4. Click Next.

  3. On the ESA Settings tab, select a login option:

  4. On the Mapping tab, map the objects in the Agiloft KB with the external Salesforce system. To map an object:

    1. Locate the  Agiloft table in the list and then select the corresponding Salesforce table from the drop-down menu. 

    2. Click Map. The Field Mapping wizard opens. On the Field Mapping tab:

      1. 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.

      2. For System Update Type, choose the sync direction for the table. You can choose a different sync direction for individual tables than you chose for the overall sync on the General tab, but you can't contradict the General tab setting. For example, if you chose to Update Agiloft Only on the General tab, you can't choose the Export option here to send data to Salesforce. You have three direction options:
        • Synchronize: Updates records in both systems based on the timestamp field.
        • Export: Updates records in Salesforce to match field values in  Agiloft.
        • Import: Updates records in  Agiloft to match field values in Salesforce.
      3. In Allowed Operations, choose which sync operations are allowed during the sync.
      4. Determine how the record ID is generated during the sync. This can be done by Salesforce or   Agiloft. If you generate record IDs with  Agiloft, you must also specify a prefix and a table column to store the generated IDs.
      5. If desired, select any actions you want to run after a successful sync.
      6. Choose whether to include records in related tables. Selecting this option can impact performance.
      7. For each Salesforce field you want to sync, select the corresponding  Agiloft field from the drop-down. Then, choose whether the field value is updated in  Agiloft, 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 5 below, on the Relation Mapping tab.

      8. 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.
      9. Click Next.
      10. On the Filters tab, if you need to prevent records created or updated in one system from being synced to the other, create a saved search to filter the desired records.
      11. Click Finish. The Field Mapping wizard closes.
    3. Repeat the previous steps for each table and set of fields you want to sync, and click Next when complete.
  5. 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.

    1. Set any linked field relations between mapped tables. Linked relationships between Salesforce tables must be preserved with corresponding links in Agiloft or you cannot save the sync configuration. See the second bullet of the Additional Notes section below for more information.
      Relation Mapping

    2. Choose whether the field values are updated in  Agiloft, Salesforce, or both systems.
    3. Click Next.
  6. 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].

  7. On the Export tab, customize any export settings related to the sync configuration. This allows you to transfer your sync configuration to another KB, if desired.
  8. Click Finish.

Additional Notes

Before attempting to sync with Salesforce, check the configuration of the  Agiloft 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."

    Example

    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  Agiloft, 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 Agiloft 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.

  • For all fields being synced, edit the field, go to the Options tab, and confirm the field does not require unique values.

These are necessary for the sync to save values from Salesforce that differ from  Agiloft values. The following fields are common changes in the out-of-the-box knowledgebase:

  • Lead Table: The Company / Location Name field should not require a unique value.
  • Company and Person Table: The linked field set to the Locations table should use the "Allow entries not in source table" option. 

Automating Synchronization

If you only need to synchronize  Agiloft 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.

Time-Based Automation

Use the following steps to set up a time-based rule to automate the sync process.

  1. Navigate to Setup > Sync and edit your Salesforce sync configuration.
  2. Click Next twice, and then click the Running tab.
  3. Make sure the "By Actions (Rules/Workflow)" option is selected and click Finish.
    Run Mode with By Actions selected
  4. Navigate to Setup > Rules and click New. The Rule wizard opens.
  5. On the General tab, enter a name and select the table you're syncing.
  6. On the Rule Type tab, select the "At selected time intervals" option.
  7. On the Condition tab, select the "Run once per scheduled time interval" option.
  8. On the Schedule tab, apply the rule however often you want the sync to occur.
  9. On the Action tab, click Create Sync Action. The Sync action wizard opens.
    1. Enter an action name.
    2. Click the lookup icon for the External System ID field and select your sync configuration.
    3. Click Finish. The Sync action wizard closes.
  10. Click Finish in the Rule wizard.

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.

Real-Time Automation

You can automatically push updates between  Agiloft 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.

Agiloft to Salesforce

You can sync individual records with Salesforce by configuring a Sync action in Agiloft 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:

  • Selecting records in the table view and using the Actions option in the action bar
  • Creating an Action Button to use inside individual records
  • Using a rule targeting a specific record or set of records

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 General tab, make sure the "Include records in related tables" checkbox is selected.
  • On the Mapping tab, open each table mapping and make sure "Search for related records" is selected on the Field Mapping tab.

Salesforce to  Agiloft

On the Salesforce side, you can use these URL parameters to sync a particular record: &explicittables=SF_TABLE_NAME&explicitids=SF_RECORD_ID

Follow the steps below to initiate a sync with  Agiloft when a record is changed in Salesforce, where only that changed record is synced.

  1. In  Agiloft, create a new user record that Salesforce can use to access the system for sync. If you assign an existing user that is used in other processes, Salesforce might attempt to sync while that account is in use, and the sync will not work. Make sure the user has full permissions to every table included in the Salesforce sync.
  2. Go to Setup > Sync and edit the Salesforce sync configuration.
  3. Copy the External System ID Prefix value, without the KB name, and paste it in a text editor where you can access it later, such as Notepad.
  4. In the same program, paste this template: 

    https://[server_name]/gui2/login.jsp?keyID=0&user=[user_name]&passwd=[passwd]&KB=[KB]&action=sync&externalSystemID=[externalSystemID]

  5. Modify the template to suit your system by changing each of the placeholder values:
    • [server_name] - your server name, such as demo.agiloft.com
    • [user_name] - the username for the user record you created in step 1
    • [passwd] - the password for the user
    • [KB] - the KB name, such as Demo
    • [externalSystemID] - the external ID prefix you copied in step 3
  6. Go to Setup > Access and click Automatic Login Hotlinks.
  7. Copy the modified URL template and paste it into the Encrypt Hotlink field.
  8. Set the desired Expiration value. When the hotlink expires, you need to use these steps again to update your configuration in Salesforce, so choose the expiration time with that in mind.
  9. Click Encrypt.
  10. Copy the encrypted hotlink and paste it into your notes for future reference.

With this information from  Agiloft, you can proceed to Salesforce.

  1. Log in to Salesforce and go to Setup > Security Controls > Remote Site Settings.
  2. Click New Remote Site.
  3. Give it a name and in the Remote Site URL field, enter your server's base URL, such as https://demo.agiloft.com.
  4. Click the gear icon in the top right and click Developer Console.
  5. If you've already configured your system to sync Salesforce files, and you've already created the AgiloftCallSyncUrl apex class, edit the class. Otherwise, go to File > New > Apex Class, name the class "AgiloftCallSyncUrl", and click OK.
  6. 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 Agiloft sync configuration:

    AgiloftCallSyncUrl class
    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 ');
    			}         
    		}
    }
  7. Make sure you've placed your encrypted hotlink instead of the placeholder at the beginning of the code block.
  8. Make sure you've listed all the Salesforce tables that you want to use with real-time sync.
  9. Select File > Save to save the class.
  10. Next, you need to create an Apex Trigger for each Salesforce table you want to sync. Follow these steps for each table: 
    1. Go to File > New > Apex Trigger and name the trigger "[table name]_sync_trg". For example, to sync the Account table, name the trigger Account_sync_trg.
    2. In the sObject field, select the Salesforce table.
    3. Click Submit.
    4. 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.

      Account table
      trigger Account_sync_trg on Account (after insert, after update) {
      	System.debug('Trigger Account_sync_trg start');
      	Account rec;         
      	rec = Trigger.new[0];
      	List<ID> idsListToSync = new List<ID>();
      	idsListToSync.add(rec.Id);
      		AgiloftCallSyncUrl.callSyncOnListId(idsListToSync);
      	System.debug('Trigger Account_sync_trg end');
      }
      Attachment table
      trigger Attachment_sync_trg on Attachment (after insert, before delete, after update) {
          System.debug('Trigger Attachment_sync_trg start');
          Attachment rec;         
          if(Trigger.isInsert || Trigger.isUpdate) {
              rec = Trigger.new[0];
          }
          if(Trigger.isDelete) {
              rec = Trigger.old[0];
          }
          Attachment parentDetails = [SELECT ParentId FROM Attachment Where Id = :rec.Id];	
          List<ID> idsListToSync = new List<ID>();
          idsListToSync.add(parentDetails.ParentId);
      	AgiloftCallSyncUrl.callSyncOnListId(idsListToSync);
          System.debug('Trigger Attachment_sync_trg end');
      }
      ContentDocument table
      trigger ContentDocument_sync_trg on ContentDocument (after insert, before delete, after update) {
          System.debug('Trigger ContentDocument_sync_trg start');
          ContentDocument rec;
          if(Trigger.isInsert || Trigger.isUpdate) {
             rec = Trigger.new[0];
          }
          if(Trigger.isDelete) {
             rec = Trigger.old[0];
          }
         
          List<ID> idsListToSync = new List<ID>();
          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');
      }
      
      ContentDocumentLink
      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');
      }
      
      FeedItem table
      trigger FeedItem_sync_trg on FeedItem (before delete) {
          System.debug('Trigger FeedItem_sync_trg start');
          FeedItem rec;
       	if(Trigger.isInsert || Trigger.isUpdate) {
              rec = Trigger.new[0];
          }
          if(Trigger.isDelete) {
              rec = Trigger.old[0];
          }
      	List<ID> idsListToSync = new List<ID>();
          idsListToSync.add(rec.ParentId);
      	AgiloftCallSyncUrl.callSyncOnListId(idsListToSync);
          System.debug('Trigger FeedItem_sync_trg end');
      }
      
    5. Select to File > Save to compile and save the trigger.

Sync Salesforce Files

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  Agiloft, 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 Agiloft field has Enable Versioning set to Yes on the Options tab of the Field wizard. Otherwise, changes from Salesforce to Agiloft are only compared to the first sync.

To set up Files sync:

  1. In Salesforce, click the gear icon in the top right and click Developer Console.
  2. Go to File > New > Apex Class.
  3. Name the class "AgiloftCallSyncUrl" and click OK.
  4. For the class, enter the following code, customizing it to use your set of synchronized tables:

    AgiloftCallSyncUrl class
    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);
        }   
    }
  5. Select File > Save to save the class.
  6. Next, you need to create an Apex Trigger for Salesforce's ContentDocumentLink table, in order to update the File's parent record on each File update. Go to File > New > Apex Trigger
  7. Name the trigger "ContentDocumentLink_sync_trg" and select ContentDocumentLink for the sObject field.
  8. Click Submit.
  9. In the console, enter the following code, customizing it to use your set of synchronized tables:

    ContentDocumentLink table
    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');
    }
  10. Select to File > Save to save the trigger.