# Notes on using Salesforce

### Accessing Fields Not Listed in Dropdown

Due to the number of Salesforce API endpoints all potential fields will not necessarily be drop down options.
Even if your desired dropdown option is not listed in most cases you can still use Salesforce formula directly within the field value itself to retrieve your desired endpoint.

### Handling Leads in Salesforce

You can find a list of records from Salesforce, using the "Find Records" operation.
![salesforce-find-leads-specify-fields](https://tray.ai/documentation/images/connectors/service/salesforce/c56d6bf8-f7fa18df_salesforce-find-leads-specify-fields.png)
Once a lead is *converted*, you **cannot** update them in any way, and they are no longer available in the Salesforce interface.
Behind the scenes in the API, Salesforce uses a "Converted" field which is either true or false. You can use this in the Salesforce connector to get a list of leads who haven't been converted yet, for example.
![salesforce-leads-not-converted](https://tray.ai/documentation/images/connectors/service/salesforce/c56d6bf8-2b154dd7_salesforce-leads-not-converted.png)
Going a step further, leads can be converted into Contacts, Opportunities, and Accounts. In tray it's possible to get the "ID" of the relevant new converted objects using the "Converted Account ID", "Converted Opportunity ID" and "Converted Contact ID" fields.
![salesforce-get-converted-ids](https://tray.ai/documentation/images/connectors/service/salesforce/c56d6bf8-f2e37d34_salesforce-get-converted-ids.png)

### Data processing

When using Salesforce in production workflows, you will likely be dealing with large amounts of data - processing hundreds or thousands of rows.
In this case, you will likely need to make use of [callable workflows](https://tray.ai/documentation/connectors/trigger/callable-trigger/) to send data for sub-processing. Learn more about this in our [course on callable workflows](https://youtu.be/Qq1eWhVld7s) from the Tray Academy.
You also may need to use our [CSV Editor](https://tray.ai/documentation/connectors/core/csv-editor/) as an intermediate storage medium, before making use of the batch/bulk operations.
When dealing with large amounts of data in Salesforce it is important to be aware of:

* What are the API limits according to your account, in terms of how many API calls can be made per day. Please the [Salesforce API limits page](https://developer.salesforce.com/docs/atlas.en-us.salesforce_app_limits_cheatsheet.meta/salesforce_app_limits_cheatsheet/salesforce_app_limits_platform_api.htm) for guidance on this
* How can you use Tray to manage the amount of API calls that are being made?
  In order to address the second point, you will see that certain operations for **pulling data from Salesforce** offer pagination options which allow you to grab batches of data in one and then loop through them with Tray, instead of making repeated single calls. The section below on [Pulling data and pagination](https://tray.ai/documentation/connectors/service/salesforce/notes-on-using-salesforce/#pulling-data-and-pagination) takes you through this in detail.
  When **pushing data to Salesforce** you may need to make use of the batch create / update and bulk upsert operations. These allow you to reduce hundreds of record updates to a single call, and so are extremely important in terms of managing your API limits. Please the section on [Pushing data and batch / bulk operations](https://tray.ai/documentation/connectors/service/salesforce/notes-on-using-salesforce/#pushing-data-and-batch--bulk-operations) for detailed explanations of this.

### Batch Operations input structure

The input structure for batch operations varies depending on which operation is being used:

### 'Batch create list' structure

As mentioned above, when using the 'Batch create' operation, the input for the **Batch create list** must be in the format of an **array of arrays**.
For example if your batch create list is set to pull a list of leads to create from data storage:
![sf-batch-create-list-props-panel](https://tray.ai/documentation/images/connectors/service/salesforce/c56d6bf8-7540f4c6_sf-batch-create-list-props-panel.png)
Then the input from debug would look like the following - if you were creating 2 new leads.
Note that each array is an array of key / value pairs:

```json
\{
 "batch_create_list": [
  [
   \{
    "value": "Levicount",
    "key": "LastName"
   \},
   \{
    "value": "Strosin-Crooks",
    "key": "Company"
   \},
   \{
    "value": "Open - Not Contacted",
    "key": "Status"
   \}
  ],
  [
   \{
    "value": "Francesconi",
    "key": "LastName"
   \},
   \{
    "value": "Wisozk-Cormier",
    "key": "Company"
   \},
   \{
    "value": "Open - Not Contacted",
    "key": "Status"
   \}
  ]
 ],
 "error_handling": "rollback_fail",
 "object": "Lead"
\}
```

### 'Batch update list' structure

As mentioned above, when using the 'Batch update' operation, the input for the **Batch update list** must be in the format of an **array of objects**.
For example if your batch create list is set to pull a list of accounts to update from data storage:
![sf-batch-update-list-props-panel](https://tray.ai/documentation/images/connectors/service/salesforce/c56d6bf8-872d451e_sf-batch-update-list-props-panel.png)
Then the input from debug would look like the following - if you were updating 3 accounts.
Note that each object contains an **object\_id** on the same top level as a **fields** array of key / value pairs:

```json
\{
 "batch_update_list": [
  \{
   "fields": [
    \{
     "value": "318-553-7145",
     "key": "Phone"
    \},
    \{
     "value": "Xinglong",
     "key": "BillingCity"
    \}
   ],
   "object_id": "0014J000007k2moQAA"
  \},
  \{
   "fields": [
    \{
     "value": "974-419-3972",
     "key": "Phone"
    \},
    \{
     "value": "Sveg",
     "key": "BillingCity"
    \}
   ],
   "object_id": "0014J000006m8EVQAY"
  \},
  \{
   "fields": [
    \{
     "value": "777-688-5363",
     "key": "Phone"
    \},
    \{
     "value": "Xingang",
     "key": "BillingCity"
    \}
   ],
   "object_id": "0014J000006m89GQAQ"
  \}
 ],
 "object": "Lead"
\}
```

### 'Batch delete' structure

When using the 'Batch delete' operation you only need to provide a simple array of IDs:

```json
"batch_delete_list": [
"0014J000007k2moQAA",
"0014J000006m8EVQAY",
"0014J000006m89GQAQ"
]
```

### Handling errors from batch updates

### Intro

It is important to take certain steps to handle errors with the **Batch Update** operation which may pass undetected.
This is because, even though some error statuses might be returned by the Salesforce API, the fact that a response has been returned at all is treated as a successful execution by the Tray.io Salesforce connector.
For example a status of **404: NOT FOUND** might be returned and, as you can see, in the debug output it has still returned green:
![salesforce-404-error](https://tray.ai/documentation/images/connectors/service/salesforce/c56d6bf8-c1ba8996_salesforce-404-error.png)

### Example

So you will need to set up a system for dealing with these errors. A very rudimentary approach would be a workflow which takes the following steps:

1. Uses a boolean to check if the above output log returns **hasErrors: true**
2. Sends a timestamp of the batch run to Slack
3. Loops through the results from the Salesforce output
4. Extracts all those whose status was not **204**
5. Sends a message to a batch errors channel in Slack for each account/lead error etc.
   ![salesforce-batch-error-workflow](https://tray.ai/documentation/images/connectors/service/salesforce/c56d6bf8-5a2403b2_salesforce-batch-error-workflow.png)

### End result

The end result would be notifications such as:
![salesforce-batch-error-slack-output](https://tray.ai/documentation/images/connectors/service/salesforce/c56d6bf8-e5e9c951_salesforce-batch-error-slack-output.png)
Another approach might be to use a script like the following which would dig back into the original update list you had compiled to replicate the info that was sent to Salesforce:
![salesforce-batch-error-script](https://tray.ai/documentation/images/connectors/service/salesforce/c56d6bf8-80d816bf_salesforce-batch-error-script.png)
And produce a result such as:
![salesforce-batch-error-slack-output](https://tray.ai/documentation/images/connectors/service/salesforce/c56d6bf8-b20a7ecf_salesforce-batch-error-slack-output-2.png)

### Additional metadata

> **Info:** **PLEASE NOTE**: Each object in the **Batch Update array contains additional metadata**, with field values nested inside a fields attribute.

```js
// You can reference the input variables using input.NAME
exports.step = function(input) \{
	return _.map(input.rows, function(row) \{
  return \{
      "object_id": row.contact_id,
      "fields": [
        \{
          "key": "Role__c",
          "value": row.role
        \}
      ]
    \}
  \});
\};
```

### Create &/ Update Flow Names

These operations will automatically set up your Salesforce Flows for you.

* \*\*On Record Create \*\*
* **On Record Update**
* **On Record Create or Update**
  Be aware that your Flow name will be autogenerated. Your Flow will be available to view in the **All Flows** page.
  The name of your Flow will be based on the \*\*Object prefix \*\*(which is automatically set to "Tray"), the **name of the operation** itself, and will **include a random hash** following it.
  Note that the Object prefix can be changed under the **Show Advanced Properties** button in the trigger's property panel.

### SOQL Query & SOSL Query operations

The Salesforce Object Query Language (SOQL) and Salesforce Object Search Language (SOSL) APIs can be used to search your organisation’s Salesforce data.
Generally speaking when using these query operations, you should use SOQL to query one operation at a time, and SOSL when you want to search text, email, phone fields, etc (basically when searching multiple objects simultaneously). This is why the latter is better to use when querying a relationship between objects and such.
Here are some points you need to consider when choosing between the two query operations:
**Use SOQL when you know which objects the data resides in**, and you want to:

* Retrieve data from a single object or from multiple objects that are related to one another.
* Count the number of records that meet specified criteria.
* Sort results as part of the query.
* Retrieve data from number, date, or checkbox fields.
  **Use SOSL when you don’t know which object or field the data resides in**, and you want to:
* Retrieve data for a specific term that you know exists within a field. Because SOSL can tokenize multiple terms within a field and build a search index from this, SOSL searches are faster and can return more relevant results.
* Retrieve multiple objects and fields efficiently where the objects might or might not be related to one another.
* Retrieve data for a particular division in an organization using the divisions feature.
* Retrieve data that’s in Chinese, Japanese, Korean, or Thai. Morphological tokenization for CJKT terms helps ensure accurate results.
  The language used is crucial for your search patterns. In principal, it is extremely similar to SQL in text style.
  With the above taken into consideration, below is an example of a SOQL query, which also involves using the Date & Time helper connector (notice how the 'Format' has been deliberately set up to suit Salesforce protocols):
  ![salesforce-soql-date-time](https://tray.ai/documentation/images/connectors/service/salesforce/c56d6bf8-888ebad9_salesforce-soql-date-time.png)
  This image demonstrates the way one may use the SOSL query operation to run through 'All Fields' available, in order to return any and all account names and IDs found (unlike the above query example, this is tailored towards multiple )
  ![salesforce-sosl-example](https://tray.ai/documentation/images/connectors/service/salesforce/c56d6bf8-49d68d27_salesforce-sosl-example.png)
  For further information regarding the Salesforce Object Query Language (SOQL) and Salesforce Object Search Language please see their [API Documentation](https://developer.salesforce.com/docs/atlas.en-us.soql_sosl.meta/soql_sosl/sforce_api_calls_soql_sosl_intro.html) site for more details.

## Managing API limits

### Pulling data and pagination

When using certain operations such as the 'Find Records' operation, you will find there are **'pagination'** options which allow you to **pull results from Salesforce in batches**.
This means you can limit the number of API calls you make - e.g. you can retrieve 3 batches of 2000 records which match your criteria, instead of making 6000 individual calls for each record.
The following workflow shows a basic pagination system where we are pulling batches of leads from Salesforce with a rating of 'warm' in order to loop through them and send them to another third-party service:
![pagination-complete-wf](https://tray.ai/documentation/images/connectors/service/salesforce/c56d6bf8-9479b847_pagination-complete-wf.png)
For a full explanation and pre-built pagination workflow please see our 'Paginate through Salesforce Records' template:
You can adjust this template to your exact needs any time you need to paginate through batches of pulled SFDC records.

### Pushing data and batch / bulk operations

As mentioned above, when you are **pushing** large batches of data to Salesforce, you will need to control the rate at which you do so in order not to exceed your API limits.

* **'Bulk upsert'** is effectively two operations in one - if a record of a particular type (e.g. 'lead') is found then it will be updated, if it is not found then a new one **of that type** (e.g. 'lead') will be created
* **'Batch Update' / 'Batch Create'** can be used together when, in the case of a record of a particular type (e.g. 'account') not being found, you wish to create a record of a different type (e.g. 'lead')

### Bulk upsert

The Salesforce bulk upsert operation only accepts data as a CSV file.
The following workflow screenshot shows how you can do this.
It demonstrates what you need to do in order to process a json array of data so that it is in CSV file and meets the Salesforce input schema requirements:

1. It uses the data mapper to make sure that all keys are in the correct format (e.g. maps `first_name` to `FirstName`
2. It uses a script step to convert the json array to CSV
3. It uses the File Helpers 'create file from content' operation to create a csv file
4. It uses the output from the File Helpers step as the input for the CSV file field in the Salesforce Bulk upsert step
   ![salesforce-bulk-upsert](https://tray.ai/documentation/images/connectors/service/salesforce/2eU8Twzp6MrlCrIKGC1k6G_salesforce-bulk-upsert.png)
   So this process could convert a json array such as:

```json
[
  \{
    "first_name": "Sonny",
    "last_name": "Castella",
    "email": "scastella0@pbs.org"
  \},
  \{
    "first_name": "Krystyna",
    "last_name": "Hannaby",
    "email": "khannaby1@homestead.com"
  \},
  \{
    "first_name": "Mel",
    "last_name": "Plumb",
    "email": "mplumb2@si.edu"
  \}
]
```

To a CSV file such as:
`FirstName,LastName,Email\nSonny,Castella,scastella0@pbs.org\nKrystyna,Hannaby,khannaby1@homestead.com\nMel,Plumb,mplumb2@si.edu`
And send it to the Salesforce Bulk API for processing.

### Polling a bulk upsert

When a bulk operation is used, Salesforce does not process the data immediately, instead it starts a bulk data load job. The time in which it takes for this job to finish depends on resources available in your Salesforce instance.
When you use a bulk data operation it receives a `Job ID` (or just `ID` as shown in the connector step output).
This job `ID` can then be used to poll for the status of the job. Only when the job shows a status of `JobComplete` has the data been successfully processed in Salesforce.
The following workflow shows a Bulk Upsert job has been started - pulling a CSV file from a trigger:
![salesforce-polling-check](https://tray.ai/documentation/images/connectors/service/salesforce/c56d6bf8-7953d4fd_salesforce-polling-check.png)
The **Repeat polling call** step uses the 'Loop Forever' operation.
The **Poll SF - check job status** step uses the 'Get job info' operation. It pulls in the 'Job ID' using the `$.steps.salesforce-1.id` jsonpath
On each iteration of the Loop, we check if the job has succeeded by looking at the `state` field. If the job has completed it will show a status of `JobComplete`:
![salesforce-boolean-check](https://tray.ai/documentation/images/connectors/service/salesforce/c56d6bf8-985e8b1d_salesforce-boolean-check.png)
**Is job complete?** is a boolean step which checks if `$.steps.salesforce-2.state` is equal to `JobComplete`.
![sf-bulk-job-statuses](https://tray.ai/documentation/images/connectors/service/salesforce/c56d6bf8-a4ae0f2d_sf-bulk-job-statuses.png)
The TRUE branch of the boolean contains the **Break loop** step (referring to the correct loop! 'loop-1' in this case).
The FALSE branch of the boolean has the **Delay and repeat loop** step:
![salesforce-boolean-delay](https://tray.ai/documentation/images/connectors/service/salesforce/c56d6bf8-fd21e4e8_salesforce-boolean-delay.png)
You can set the delay to be e.g. 1 minute before the next check.
A key point here is that each check is an API call, so if you are running a large update job you don't want to be checking every 10 seconds!
**Get Job Information**
Add a final Salesforce connector step to your workflow. This is used to gather the job information (how many file uploads, which failed, time taken etc). Set the operation to 'Get job info' and the 'Job ID' to: `$.steps.salesforce-1.id`.
In your Debug panel you should see results similar to below:
![salesforce-job-info](https://tray.ai/documentation/images/connectors/service/salesforce/c56d6bf8-7c1dece7_salesforce-job-info.png)

### Batch update / batch create

As mentioned above, there may be a bulk upload scenario where, **if a record is not found then you want to create a record of a different type** (which is not possible with 'Bulk upsert').
This section will be a very simple demo of using a combination of the 'Batch update records' and 'Batch create records' operations to achieve this.

> **Warning:** **IMPORTANT!** The Salesforce API will only receive batch update / create lists in an exact format, as explained in the above [note on batch operations input structure](#batch-operations-input-structure). The below example shows a way of formatting your input using the Object Helpers 'JSON parse' operation.

> **Warning:** **IMPORTANT!** When processing batches of data using Data Storage lists, you need to be conscious of the fact that the storage limit under one key is 400K. One way to handle this is for batches to be sent for parallel processing to a callable workflow which uses 'current run' data storage that is cleared after each run (as discussed in [ our guide to workflow threads ](https://tray.ai/documentation/platform/automation-integration/advanced-use-cases/workflow-threads)  ). Another approach might be to use the [ CSV Editor ](https://tray.ai/documentation/connectors/core/csv-editor/).

The scenario is that a callable workflow is receiving batches of data such as the following:
![batch-create-update-data-sheet](https://tray.ai/documentation/images/connectors/service/salesforce/c56d6bf8-232a214e_batch-create-update-data-sheet.png)
The idea is that all records **with an object\_id** are **pre-existing Salesforce Accounts** that need to be updated with Phone and BillingCity.
While records **without an object\_id** do not yet exist in Salesforce and we need to turn them into **Leads** which might become Accounts at a later stage.
The screenshot below shows a callable workflow which is receiving these batches of records:
![batch-create-update-callable-workflow](https://tray.ai/documentation/images/connectors/service/salesforce/c56d6bf8-2722c01b_batch-create-update-callable-workflow.png)
The data being received by the **Callable Trigger** is in json format:
![batch-create-update-json-trigger](https://tray.ai/documentation/images/connectors/service/salesforce/c56d6bf8-ba5cb973_batch-create-update-json-trigger.png)
The following steps are taken through the course of the workflow:

### Loop Collection

**Loop collection** uses `$.steps.trigger.records` to loop through the records received by the trigger

### Contains SF id?

The boolean step **Contains SF id?** checks if **object\_id** exists:
![batch-create-update-contains-sf-id](https://tray.ai/documentation/images/connectors/service/salesforce/c56d6bf8-74ffdc33_batch-create-update-contains-sf-id.png)

### Create 'create'/'update' objects

Depending on the result the True and False paths then add the current record to either a 'create-list' or an 'update-list'
A key point here is that we must make sure the record objects being created **adhere to the key/value pair structure required by Salesforce**
Depending on how complex the data you are receiving is, you may need to use a script connector to process your data, as discussed in [processing batch updates](https://tray.ai/documentation/platform/automation-integration/advanced-use-cases/batching-queueing/)
For the purposes of this demo, however, we can achieve this with an Object Helpers step, using 'JSON parse':
![create-update-object-key-value-pairs](https://tray.ai/documentation/images/connectors/service/salesforce/c56d6bf8-157761e7_create-update-object-key-value-pairs.png)

### 'Create' object structure

For a 'create' object the structure is:
![create-object-structure-output](https://tray.ai/documentation/images/connectors/service/salesforce/c56d6bf8-02e93061_create-object-structure-output.png)

### 'Update object structure'

And for an 'update' object the structure is:
![update-structure-object-output](https://tray.ai/documentation/images/connectors/service/salesforce/c56d6bf8-8a5918db_update-structure-object-output.png)

### Append to create/update lists

The data storage **Append to create-list** and **Append to update-list** steps use the 'Append to list' operation:
![data-storage-create-list-update-list](https://tray.ai/documentation/images/connectors/service/salesforce/c56d6bf8-bf8df2d5_data-storage-create-list-update-list.png)

### Get create/update lists

The subsequent **Get create-list** and **Get update-list** data storage steps then use **Get value** to retrieve these lists
![data-storage-get-create-list-update-list](https://tray.ai/documentation/images/connectors/service/salesforce/c56d6bf8-74bb87bf_data-storage-get-create-list-update-list.png)

### Check if create/update lists are empty

Both the **Leads to create?** and **Accounts to update** boolean steps check if these lists are returning empty arrays

### Batch create

The **Batch Create** operation then uses the resulting 'create-list' from data storage:
![batch-create-operation](https://tray.ai/documentation/images/connectors/service/salesforce/c56d6bf8-502fb8f7_batch-create-operation.png)
Crucially, the structure of the input list conforms exactly to what is required by Salesforce:
![batch-create-input](https://tray.ai/documentation/images/connectors/service/salesforce/c56d6bf8-529c2d3c_batch-create-input.png)

### Batch update

And the **Batch Update** operation uses the resulting 'update-list' from data storage:
![batch-update-operation](https://tray.ai/documentation/images/connectors/service/salesforce/c56d6bf8-d3543aab_batch-update-operation.png)
Again, the structure of the input list conforms exactly to what is required by Salesforce:
![batch-update-input](https://tray.ai/documentation/images/connectors/service/salesforce/c56d6bf8-e273e14d_batch-update-input.png)

## Duplicating & Merging

### Deduplicating & merging Salesforce Records

Over time you may find that duplicate records build up in your Salesforce database.
The following workflow imagines a scenario whereby new leads being created need to be checked for pre-existing duplicates:
![deduplicate-workflow](https://tray.ai/documentation/images/connectors/service/salesforce/c56d6bf8-269abffe_deduplicate-workflow.png)

1. The **Listen for Lead Creation** triggers the workflow when a new lead is created in Salesforce
2. **Get New Lead** uses the 'Find records' operation, using the `$.steps.trigger.events[0].Id` jsonpath to retrieve the newly created lead, including all the relevant associated fields (FirstName, LastName, Email, Phone, Lead ID, Company etc.)
3. We then conduct a series of 3 checks to see if this lead already exists as a duplicate (explained in detail below)
4. If any of these checks find a duplicate, the Salesforce id for the duplicate lead is stored using a Data Storage **set id** step ('Set Value operation'), and the workflow moves on to the de-duplicating stage
5. If all 3 checks fail to find a duplicate then the workflow is terminated, as no action needs to be taken
6. If deduplicating is required, the Data Storage **get id** step uses 'Get Value' to retrieve the id of the duplicate lead
7. **Get Old Record** is a 'Find Records' operation which uses `$.steps.storage-3.value` to retrieve the duplicate lead, including all the relevant associated fields (FirstName, LastName, Email, Phone, Lead ID, Company etc.)
8. **Build Merged Lead Values** is a script which then replaces all the old duplicate values for name, email, etc. with the values from the new lead. If they are not present for the new lead then the original old values will be used
9. **Merge Leads** finally uses the Salesforce 'Merge Records' operation with the new Lead ID (`$.steps.trigger.events[0].Id`) as the Master record ID and the old Lead ID (`$.steps.storage-3.value`) as the 'Record to merge'. The 'Fields to populate' come from the result of the merged values script (`$.steps.script-1.result`)

### The duplicate checks explained

When making the above duplicate checks, we are making a series of checks of varying degrees of certainty.
The first check is by email:
![duplicate-check-by-email](https://tray.ai/documentation/images/connectors/service/salesforce/c56d6bf8-d1741bbb_duplicate-check-by-email.png)
Looking at the properties panel, we can see how this check is set up:
![duplicate-check-by-email-props-panel](https://tray.ai/documentation/images/connectors/service/salesforce/c56d6bf8-eeef9d6e_duplicate-check-by-email-props-panel.png)
You can see that while a check is being made to see if any existing leads match this new lead by email, we are also checking for First Name, Last Name and Vertical (i.e. automotive, banking, consumer etc.)
The subsequent checks by phone and company use the same conditional setup, replacing email with phone and company.
By the end of this process we will have checked with approx 99% degree of certainty for pre-existing duplicates (these aren't absolute failsafes as there could be a typo in each field we are checking)

### Merging the new and duplicate record

After using the Data Storage **get id** step to retrieve the id of the identified duplicate, we fetch the duplicate lead itself:
The **Build Merged Lead Values** script step then pulls in the old Lead values and new Lead values as variables:
![script-oldlead-newlead-variables](https://tray.ai/documentation/images/connectors/service/salesforce/c56d6bf8-8c84d6ac_script-oldlead-newlead-variables.png)
The script itself is simple:

```js
// You can reference the input variables using input.NAME
// Parsed JSON files could be referenced as fileInput
exports.step = function(input, fileInput) {
 let values = [];

 for(let key in input.newLead){
  if(key !== "Id"){
   if(input.newLead[key] === null){
    values.push(\{
     "name": key,
     "value": input.oldLead[key]
    \});
   } else {
    values.push(\{
     "name": key,
     "value": input.newLead[key]
    \});
   }
  }
 }

 return values;
```

It will replace all values from the old lead with values from the new lead.
If the new lead does not have a particular value, then the old lead value will be retained.
The output is then in a format which will be accepted by the Salesforce schema for merging records. For example:

```json
{
 "result": [
  \{
   "name": "FirstName",
   "value": "Roger"
  \},
  \{
   "name": "LastName",
   "value": "Ramjet"
  \},
  \{
   "name": "Phone",
   "value": "(850) 777-2436"
  \},
  \{
   "name": "Id",
   "value": "00Q1QxxxxxxYGUA3"
  \},
  \{
   "name": "Vertical__c",
   "value": "Automotive"
  \},
  \{
   "name": "Company",
   "value": "Ramjet and co"
  \},
  \{
   "name": "Email",
   "value": "info@ramjet.net"
  \},
  \{
   "name": "MobilePhone",
   "value": "(850) 755-3555"
  \}
 ],
 "console": []
}
```

The output of the script step can then be used in **Fields to populate** for the **Merge leads** step:
![merge-leads](https://tray.ai/documentation/images/connectors/service/salesforce/c56d6bf8-5055d9ae_merge-leads.png)

***

![salesforce-raw-http](https://tray.ai/documentation/images/connectors/service/salesforce/c56d6bf8-a8fb5fc9_salesforce-raw-http.png)

## Detailed examples

### Salesforce and Trello

Below is an example of a way in which you could potentially use the Salesforce trigger and connector, to integrate with Trello. In this imagined scenario, upon creation of a Salesforce record, the workflow checks who created said card and if it was a Partner referral. Should this be the case, the Tray workflow will set a time limit for contact to be made.
The steps will be as follows:

1. Setup the trigger and the first connector step in order to get the new record information.
2. Create a boolean condition to base your output on.
3. If a partner referral is not confirmed, create a Trello card as standard.
4. If a partner referral is confirmed, set a contact due date on said Trello card.
   The final outcome should look like this:
   ![salesforce-example-1-complete-wf](https://tray.ai/documentation/images/connectors/service/salesforce/c56d6bf8-cd4ede94_salesforce-example-1-complete-wf.png)

### 1 - On Record Create

Once you have clicked 'Create new workflow' on your main Tray.io dashboard (and named said new workflow), select the 'Salesforce Trigger' from the trigger options available:
![salesforce-trigger-select](https://tray.ai/documentation/images/connectors/service/salesforce/c56d6bf8-f205b9b8_salesforce-trigger-select.png)
Once you have been redirected to the Tray.io workflow dashboard, choose your Salesforce authentication, and set the operation to 'On Record Create'. You will see the option to select 'Record type' is mandatory. Choose from the dropdown options available: 'Lead'.
![salesforce-trigger-lead](https://tray.ai/documentation/images/connectors/service/salesforce/c56d6bf8-f8e119ae_salesforce-trigger-lead.png)

> **Info:** **PLEASE NOTE:** If you click on 'Show advanced properties' you will see the 'Object prefix' option mentioned earlier in the Trigger [White-labelling the trigger](https://tray.ai/documentation/connectors/service/salesforce/sf-trigger/#white-labelling) section of this page.

### 2 - Find Records

![salesforce-find-records-lead](https://tray.ai/documentation/images/connectors/service/salesforce/c56d6bf8-7c7ca460_salesforce-find-records-lead.png)
The first Salesforce connector step uses 'Find records' and sets the 'Record type' to 'Lead'.
While this step will seem similar to the trigger, its purpose is different. The trigger is activated upon a new Lead record being created. This connector step finds said record to get the record info.
![salesforce-find-records-lead](https://tray.ai/documentation/images/connectors/service/salesforce/c56d6bf8-7c7ca460_salesforce-find-records-lead.png)
Below this section is an input field that is mandatory - 'Conditions matching'. As this is pre-filled to 'Match all conditions' you need not worry about it for this example.

### 3 -Partner Referral?

![salesforce-boolean](https://tray.ai/documentation/images/connectors/service/salesforce/c56d6bf8-46706396_salesforce-boolean.png)
Add another connector step to your workflow, this time utilising the Boolean core connector.
It is here that the conditions are set regarding the two alternative outputs we intend to make.
The first input will be: `$.steps.salesforce-1.records[0].LeadSource`. This means that the the `LeadSource` found in the step previous will be what is tested.
The 'Comparison type' is set to: `Equal to` which is self explanatory. The final section is what the `LeadSource` is queried against: `Partner Referral`.

> **Info:** **PLEASE NOTE**: Make sure to double check your input selectors are set accurately! The first condition is set as a jsonpath, while the next two are strings. If this is not correct then your workflow will come back with an error.

### | | False Branch

### | |\_ Create new contact card

![salesforce-boolean-false](https://tray.ai/documentation/images/connectors/service/salesforce/c56d6bf8-3b462b4c_salesforce-boolean-false.png)
On the left hand branch of the boolean condition, aka the 'FALSE' branch, insert a Trello connector.
Add your Trello authentication as appropriate, and set the operation to 'Create new card'.

> **Info:** **PLEASE NOTE**: If you don't have your Trello authentication set up, please see our [Trello docs](https://tray.ai/documentation/connectors/service/trello) for more details.

Your 'Board ID' will be available from a list of dropdown options, as will your 'List ID'. The 'Name' field we can generate via utilising the connector-snake once more and linking it to "Salesforce step 1" again as it has all the information regarding said record (including its name).
Notice that the 'Due Date' is left blank here, as we have not set it (check the bottom right hand corner of the above image). While this is not a mandatory field, it will be important on the 'TRUE' branch later.

### | | True Branch

### Get current timestamp

On the right hand branch of the boolean condition, aka the 'TRUE' branch, drop and drag a Date & Time Helper connector.
This connector will be used to get the current time stamp for the workflow being processed. Set the operation to do just that by selecting 'Get current timestamp'. choose your timezone, and your preferred format as well.
![salesforce-boolean-true-1](https://tray.ai/documentation/images/connectors/service/salesforce/c56d6bf8-4aacad2c_salesforce-boolean-true-1.png)

### Set 1 day time limit

Take a second Date & Time Helper connector and place it below the first one. In this case the operation will be creating a set time: 'Add to date'. The only other field we need fill in is the 'business days' option, which in this demo will be 1 day.
![salesforce-boolean-true-2](https://tray.ai/documentation/images/connectors/service/salesforce/c56d6bf8-d6bd5123_salesforce-boolean-true-2.png)
Finally add a Trello step and fill it in as described previously. This time you may wish to highlight the fact that this was a partner referral within the card description.
Using the connector-snake once more, fill in the due date the time helpers step 2 result: `$.steps.date-time-helpers-2.result`

### Create new contact card

Your final step should be filled out as follows - you will notice that we have also included the due date in the description step as well:
![salesforce-boolean-true-3](https://tray.ai/documentation/images/connectors/service/salesforce/c56d6bf8-5ea58dae_salesforce-boolean-true-3.png)

### Final outcome

Once you run your workflow, depending on your outcome you will now auto-generate Trello cards, both will have descriptions attached as per our interpolation within the descriptions we made earlier.
Below is an example of one card that is in viewing mode, which has a partner referral and therefore also includes a due date:
![salesforce-trello-cards-duedate](https://tray.ai/documentation/images/connectors/service/salesforce/c56d6bf8-66559650_salesforce-trello-cards-duedate.png)
Congratulations! your Salesforce workflow is now complete.

## Using the Raw HTTP Request ('Universal Operation')

> **Warning:** **IMPORTANT!**: Please note that the Raw HTTP Request only works with REST API endpoints. You cannot make requests to SOAP API endpoints.

As of **version 7.5**, you can effectively create your own operations.
This is a very powerful feature which you can put to use when there is an endpoint in Salesforce which is not used by any of our operations.
To use this you will first of all need to research the endpoint in the [Salesforce API documentation](https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/), to **find the exact format** that Salesforce will be expecting the endpoint to be passed in.
Note that you will only need to add the suffix to the endpoint, as the base URL will be automatically set (the base URL is picked up from the value you entered when you created your authentication).

> **Warning:** **IMPORTANT!**: **Accessing the base URL**: If you need to access the base URL (e.g. for making a Full URL Raw HTTP requests) then the following information will be useful. The base URL for Salesforce is your Salesforce instance URL. It will most likely be similar to: **'<https://abc123.salesforce.com>'** where **'abc123'** is your Salesforce instance name. You may access your Salesforce instance URL within a workflow by using jsonpath to extract it from the authentication parameters\*\* '$.auth.instance\_url'\*\*.

For example, say that the 'Get Job Info' operation did not exist in our Salesforce connector, and you wanted to use this endpoint, you would use the Salesforce API docs to find the relevant endpoint - which in this case is a `GET` request called: `/services/data/vXX.X/jobs/ingest/jobID`.

> **Info:** **PLEASE NOTE**: You will need to enter the correct API version (e.g. 'v51.0') and the correct job ID (e.g. '7504S000001nOJkQAM') in order to build a valid endpoint for the URL.

More details can be found [here](https://developer.salesforce.com/docs/atlas.en-us.api_bulk_v2.meta/api_bulk_v2/get_job_info.htm).
![salesforce-get-url](https://tray.ai/documentation/images/connectors/service/salesforce/c56d6bf8-1c6b1105_salesforce-get-url.png)
So if you know what your method, endpoint and details of your query parameters are, you can get the Salesforce job information with the following settings:
Method: `GET`
Endpoint: `/services/data/v51.0/jobs/ingest/7504S000001nOJkQAM`
Query Parameters: None
Body Type : None : null
Final Example outcome being: **<https://abc123.salesforce.com/services/data/v51.0/jobs/ingest/7504S000001nOJkQAM>**
![salesforce-raw-http](https://tray.ai/documentation/images/connectors/service/salesforce/c56d6bf8-a8fb5fc9_salesforce-raw-http.png)
