All tutorials
Add what3words to Salesforce
This tutorial outlines how to add the what3words API to Salesforce and provides the starting point for using what3words throughout the Salesforce platform. It demonstrates how to build a simple flow that listens for changes to a what3words address field, calls the what3words API to obtain the coordinates, and then adds them to a Salesforce Geolocation field. It requires minimal development work to copy and paste Apex Classes and requires only a few changes to be made within Salesforce Setup.
This allows you to start using what3words addresses reported by users within Salesforce, such as within Service Cloud and allows your users to share precise locations with colleagues. More complex flows can then be created to use what3words addresses throughout the Salesforce platform.
In this tutorial, the example provided is for Service Cloud but the what3words address field and geolocation field can be added to any Salesforce Object. Amending the example flow and trigger to point to these objects allows for what3words addresses and coordinates to be added anywhere in Salesforce.
Prerequisites
- You will need a Salesforce account. Sign up here to get started or set up a sandbox environment.
- You will need a user account with permissions to be able to modify your Salesforce settings in Setup.
Two new fields need to be created for each object that you would like to use what3words addresses with.
- ‘what3words’ field with type Text and length 200. This is used to store the what3words address.
- ‘Location’ field with type Geolocation (decimal), This is used to store the coordinates for the what3words address in decimal degree format.
In this example, we add the fields to the Case and Contact objects for use in Service Cloud.
Click on the Object Manager tab and search for “case”.
Select “Fields & relationships” and add the 2 new fields
Repeat for the Contact object.
Next, we need to create two new Apex Classes. The classes do the following:
- Call the what3words API convert to coordinates function passing through the what3words address
- Take the response from the API and extract the coordinates
Select the Home tab in Setup and search for class and select “Apex Class”
Create a new class W3WInvocableCallout
and paste in the following code. You will need to replace API-KEY
with your what3words API key from Step 1 above.
public with sharing class W3WInvocableCallout { @InvocableMethod(label='Get W3W Coordinates' description='Returns the Latitude and Longitude of the W3W provided' category='Case') public static List<String> getW3WCoordinates(List<Case> cases) { for (Case theCase : cases) { updateCase(JSON.serialize(theCase)); } return null; } @future(callout=true) public static void updateCase(String theCaseStr ) { Case theCase = (Case)JSON.deserialize(theCaseStr, Case.class); Http http = new Http(); HttpRequest request = new HttpRequest(); String theURL = 'https://api.what3words.com/v3/convert-to-coordinates?words='+theCase.what3words__c+'&key=API-KEY'; request.setEndpoint(theURL); request.setMethod('GET'); HttpResponse response = http.send(request); // If the request is successful, parse the JSON response. if (response.getStatusCode() == 200) { // Deserialize the JSON string into collections of primitive data types. What3Words theResult = What3Words.parse(response.getBody()); theCase.Location__latitude__s = theResult.coordinates.lat; theCase.Location__longitude__s = theResult.coordinates.lng; update theCase; } } }
Create a second class what3words
and paste in the following code.
public class What3Words { public String country {get;set;} public Square square {get;set;} public String nearestPlace {get;set;} public Southwest coordinates {get;set;} public String words {get;set;} public String language {get;set;} public String mapUrl {get;set;} // in json: map public What3Words(JSONParser parser) { while (parser.nextToken() != System.JSONToken.END_OBJECT) { if (parser.getCurrentToken() == System.JSONToken.FIELD_NAME) { String text = parser.getText(); if (parser.nextToken() != System.JSONToken.VALUE_NULL) { if (text == 'country') { country = parser.getText(); } else if (text == 'square') { square = new Square(parser); } else if (text == 'nearestPlace') { nearestPlace = parser.getText(); } else if (text == 'coordinates') { coordinates = new Southwest(parser); } else if (text == 'words') { words = parser.getText(); } else if (text == 'language') { language = parser.getText(); } else if (text == 'map') { mapUrl = parser.getText(); } else { System.debug(LoggingLevel.WARN, 'What3Words consuming unrecognized property: '+text); consumeObject(parser); } } } } } public class Square { public Southwest southwest {get;set;} public Southwest northeast {get;set;} public Square(JSONParser parser) { while (parser.nextToken() != System.JSONToken.END_OBJECT) { if (parser.getCurrentToken() == System.JSONToken.FIELD_NAME) { String text = parser.getText(); if (parser.nextToken() != System.JSONToken.VALUE_NULL) { if (text == 'southwest') { southwest = new Southwest(parser); } else if (text == 'northeast') { northeast = new Southwest(parser); } else { System.debug(LoggingLevel.WARN, 'Square consuming unrecognized property: '+text); consumeObject(parser); } } } } } } public class Southwest { public Double lng {get;set;} public Double lat {get;set;} public Southwest(JSONParser parser) { while (parser.nextToken() != System.JSONToken.END_OBJECT) { if (parser.getCurrentToken() == System.JSONToken.FIELD_NAME) { String text = parser.getText(); if (parser.nextToken() != System.JSONToken.VALUE_NULL) { if (text == 'lng') { lng = parser.getDoubleValue(); } else if (text == 'lat') { lat = parser.getDoubleValue(); } else { System.debug(LoggingLevel.WARN, 'Southwest consuming unrecognized property: '+text); consumeObject(parser); } } } } } } public static void consumeObject(System.JSONParser parser) { Integer depth = 0; do { System.JSONToken curr = parser.getCurrentToken(); if (curr == System.JSONToken.START_OBJECT || curr == System.JSONToken.START_ARRAY) { depth++; } else if (curr == System.JSONToken.END_OBJECT || curr == System.JSONToken.END_ARRAY) { depth--; } } while (depth > 0 && parser.nextToken() != null); } public static What3Words parse(String json) { System.JSONParser parser = System.JSON.createParser(json); return new What3Words(parser); } }
We are now going to create a simple flow that:
- Listens for a change to the what3words field on the Case object (e.g. a user pastes an address into it)
- Uses an action and the Apex Classes to call the what3words API
- Puts the resulting coordinates into the Location field
Search for flows on the Home tab. Click “New” flow and select ”Record triggering flow”
Select the Object to be “Case” and set to trigger the flow when “A record is created or updated”. Then set the Conditions Requirement to be “Any condition is met (OR)” with the following settings:
- Field: “what3words__c”
- Operator: “Is Changed”
- Value: “True”
Click “Done”
Next click the + icon on the flow diagram to add an action.
Select Action and search for “Get W3W Coordinates” to select the Apex Class added in Step 3.
Enter a name of “Get coords” and toggle on “Include”. Then in the Cases field – select “Record” and remove the full stop so it appears as {!$Record}
.
The what3words API is an external API and therefore in order for Salesforce to call the API a security exemption needs to be added to the Security settings. Search for “Remote site settings” in the Security category and add a new remote setting for the what3words API URL https://api.what3words.com/v3
.
We will now check that the new setup is all working correctly by creating a new case in Service Cloud.
Select Service Cloud from the menu
Then select Cases dropdown and click “New’
Populate the case details and ensure you add a what3words address to the what3words field and click done.
Click on the case and the coordinates should now appear in the Location field.
Alternatively, it is possible to create a similar flow that takes the coordinates entered into the Location geolocation field and converts them to a what3words address stored within the what3words field.
The process is similar to the steps above but after creating the fields in step 2, the following Apex classes C23waInvocableCallout
and what3words
will need to be created. You will need to replace API-KEY with your what3words API key from Step 1:
public with sharing class C23waInvocableCallout { @InvocableMethod(label='Get W3W Address' description='Returns the 3 word address for coordinates' category='Case') public static List<String> getW3WAddress(List<Case> cases) { for (Case theCase : cases) { updateCase(JSON.serialize(theCase)); } return null; } @future(callout=true) public static void updateCase(String theCaseStr ) { Case theCase = (Case)JSON.deserialize(theCaseStr, Case.class); String lat = String.valueOf(theCase.Location__latitude__s); String lng = String.valueOf(theCase.Location__longitude__s); Http http = new Http(); HttpRequest request = new HttpRequest(); String theURL = 'https://api.what3words.com/v3/convert-to-3wa?coordinates='+lat+','+lng+'&key=API-KEY'; request.setEndpoint(theURL); request.setMethod('GET'); HttpResponse response = http.send(request); // If the request is successful, parse the JSON response. if (response.getStatusCode() == 200) { // Deserialize the JSON string into collections of primitive data types. What3Words theResult = What3Words.parse(response.getBody()); theCase.what3words__c = theResult.words; update theCase; } } }
public class What3Words { public String country {get;set;} public Square square {get;set;} public String nearestPlace {get;set;} public Southwest coordinates {get;set;} public String words {get;set;} public String language {get;set;} public String mapUrl {get;set;} // in json: map public What3Words(JSONParser parser) { while (parser.nextToken() != System.JSONToken.END_OBJECT) { if (parser.getCurrentToken() == System.JSONToken.FIELD_NAME) { String text = parser.getText(); if (parser.nextToken() != System.JSONToken.VALUE_NULL) { if (text == 'country') { country = parser.getText(); } else if (text == 'square') { square = new Square(parser); } else if (text == 'nearestPlace') { nearestPlace = parser.getText(); } else if (text == 'coordinates') { coordinates = new Southwest(parser); } else if (text == 'words') { words = parser.getText(); } else if (text == 'language') { language = parser.getText(); } else if (text == 'map') { mapUrl = parser.getText(); } else { System.debug(LoggingLevel.WARN, 'What3Words consuming unrecognized property: '+text); consumeObject(parser); } } } } } public class Square { public Southwest southwest {get;set;} public Southwest northeast {get;set;} public Square(JSONParser parser) { while (parser.nextToken() != System.JSONToken.END_OBJECT) { if (parser.getCurrentToken() == System.JSONToken.FIELD_NAME) { String text = parser.getText(); if (parser.nextToken() != System.JSONToken.VALUE_NULL) { if (text == 'southwest') { southwest = new Southwest(parser); } else if (text == 'northeast') { northeast = new Southwest(parser); } else { System.debug(LoggingLevel.WARN, 'Square consuming unrecognized property: '+text); consumeObject(parser); } } } } } } public class Southwest { public Double lng {get;set;} public Double lat {get;set;} public Southwest(JSONParser parser) { while (parser.nextToken() != System.JSONToken.END_OBJECT) { if (parser.getCurrentToken() == System.JSONToken.FIELD_NAME) { String text = parser.getText(); if (parser.nextToken() != System.JSONToken.VALUE_NULL) { if (text == 'lng') { lng = parser.getDoubleValue(); } else if (text == 'lat') { lat = parser.getDoubleValue(); } else { System.debug(LoggingLevel.WARN, 'Southwest consuming unrecognized property: '+text); consumeObject(parser); } } } } } } public static void consumeObject(System.JSONParser parser) { Integer depth = 0; do { System.JSONToken curr = parser.getCurrentToken(); if (curr == System.JSONToken.START_OBJECT || curr == System.JSONToken.START_ARRAY) { depth++; } else if (curr == System.JSONToken.END_OBJECT || curr == System.JSONToken.END_ARRAY) { depth--; } } while (depth > 0 && parser.nextToken() != null); } public static What3Words parse(String json) { System.JSONParser parser = System.JSON.createParser(json); return new What3Words(parser); } }
Then follow steps 4-6 but for step 4 the Conditions Requirement should be set to “Any condition is met (OR)” with the following criteria:
- Field: “Location__Latitude__s”
- Operator: “Is Changed”
- Value: “True”
OR
- Field: “Location__Longitude__s”
- Operator: “Is Changed”
- Value: “True”
When selecting the action for your flow, search for “Get W3W Address” to use the class above that obtains the what3words address for the coordinates.
The best way to determine where an issue is occurring is by running your flow in “Debug” mode. This will step through the flow and show you the outcome and any errors with each step. Additionally, if there is a problem with one of the Apex Classes administrators should receive an automated email from Salesforce with errors.
Coordinates are not populated after editing a case: Ensure you have added the security exemption in step 5 to allow Salesforce to call the what3words API.
Coordinates are not populated after editing a case: Ensure you have added your what3words key to the Apex class W3WInvocableCallout
If you encounter errors or issues related to convert-to-coordinate requests while using the Free plan, please check the network panel for the following error message Error 402 payment required
and its response, indicating the need to upgrade to a higher plan:
{ "error": { "code": "QuotaExceeded", "message": "Quota Exceeded. Please upgrade your usage plan, or contact support@what3words.com" } }
For more information, visit our API plans page. If you need further assistance, contact support@what3words.com.