Skip to content

Instantly share code, notes, and snippets.

@chrisjhoughton
Last active May 19, 2023 04:36
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save chrisjhoughton/50927078894b85ff4e24f4d968796f6f to your computer and use it in GitHub Desktop.
Save chrisjhoughton/50927078894b85ff4e24f4d968796f6f to your computer and use it in GitHub Desktop.
Notify tray workflows "webhook style" from Salesforce using Apex triggers.

Salesforce Apex trigger notifications for tray.io

Salesforce's Apex triggers allow you to trigger tray workflows in real-time, based on events that occur in Salesforce. Events include things like:

  • New lead creations
  • Opportunity updates, such as the status moving from "open" to "closed"

Salesforce doesn't support webhooks out of the box, so we'll need to add some Apex code to your Salesforce account to notify tray at the right time.

Tip: if you're not familiar with Apex code, you might want your Salesforce developer to do this step.

You'll need to change three things in two files: (You can do this in Sublime Text, Atom, or Notepad)

1. The Salesforce trigger - TrayWebhookTrigger.apex

  • All Salesforce triggers are tied to an individual "Object", like "Account", or "Opportunity". Add the object that you'd like to subscribe to in place of YourObject on line 2.
  • Every tray workflow has a unique URL. Copy this from the workflow in the tray UI and then paste it into the the url section on line 6 of WebhookTrigger.apex.

### 2. The test file - TriggerWebhookTriggerText.apex

All Salesforce Apex code needs to have tests attached to it to ensure things are stable.

  • Add the same object type as above to line 73 in place of YourObject.
  • Replace the URL on line 84 with the URL of your tray workflow.

## Adding the code to Salesforce

After you've made the edits to the files, we can upload the code to Salesforce: (Note that you'll need to be an admin to do this)

  1. Open up your Salesforce account
  2. Click "Setup" in the top right
  3. Go to Develop > Apex Classes
  4. Click "New"
  5. Copy the code from the TrayWebhook.apex file into the code editor and click "Save"
  6. Do the same above for the TriggerWebhookTriggerText.apex
  7. Navigate to Customize > the object type you want to create the trigger > Triggers, and create a new trigger, copying the code from TrayWebhookTrigger.apex.

## Security settings

You'll also need to add the full URL of the workflow as a "Remote Site" in Security > Remote Site Settings.

## Deployment

If you're running a Salesforce instance on a premium tier, you won't be able to push Apex code directly to production. You'll need to first add it to a Sandbox, and then deploy the code using a Change Set.

Visit Salesforce's documentation for more info on using Change Sets to deploy Apex.

public class TrayWebhook {
public static boolean firstRun = true;
public static String jsonContent(List<Object> triggerNew, List<Object> triggerOld) {
String newObjects = '[]';
if (triggerNew != null) {
newObjects = JSON.serialize(triggerNew);
}
String oldObjects = '[]';
if (triggerOld != null) {
oldObjects = JSON.serialize(triggerOld);
}
String userId = JSON.serialize(UserInfo.getUserId());
String content = '{"new": ' + newObjects + ', "old": ' + oldObjects + ', "userId": ' + userId + '}';
return content;
}
@future(callout=true)
public static void callout(String url, String content) {
Http h = new Http();
HttpRequest req = new HttpRequest();
req.setEndpoint(url);
req.setMethod('POST');
req.setHeader('Content-Type', 'application/json');
req.setBody(content);
h.send(req);
}
}
// CHANGE: `YourObject` to the record type that you'd like to watch for changes.
trigger TrayWebhookTrigger on YourObject (before insert,before delete,after insert,after update,after undelete,after delete,before update) {
if (TrayWebhook.firstRun) {
TrayWebhook.firstRun = false;
// CHANGE: replace this URL with the URL of your tray workflow, where you've added
// the Salesforce Notification trigger.
String url = 'https://c6670ba1-ad45-416e-8489-386c0456cbaa.trayapp.io';
String content = TrayWebhook.jsonContent(Trigger.new, Trigger.old);
TrayWebhook.callout(url, content);
}
}
@isTest
public class TrayWebhookTriggerTest implements HttpCalloutMock {
private static HttpRequest request;
private static HttpResponse response;
public HTTPResponse respond(HTTPRequest req) {
request = req;
response = new HttpResponse();
response.setStatusCode(200);
return response;
}
static SObject mock(String sobjectName) {
SObjectType t = Schema.getGlobalDescribe().get(sobjectName);
SObject o = t.newSobject();
Map<String, Schema.SObjectField> m = t.getDescribe().fields.getMap();
for (String fieldName : m.keySet()) {
DescribeFieldResult f = m.get(fieldName).getDescribe();
if (!f.isNillable() && f.isCreateable() && !f.isDefaultedOnCreate()) {
if (f.getType() == DisplayType.Boolean) {
o.put(f.getName(), false);
}
else if (f.getType() == DisplayType.Currency) {
o.put(f.getName(), 0);
}
else if (f.getType() == DisplayType.Date) {
o.put(f.getName(), Date.today());
}
else if (f.getType() == DisplayType.DateTime) {
o.put(f.getName(), System.now());
}
else if (f.getType() == DisplayType.Double) {
o.put(f.getName(), 0.0);
}
else if (f.getType() == DisplayType.Email) {
o.put(f.getName(), 'foo@foo.com');
}
else if (f.getType() == DisplayType.Integer) {
o.put(f.getName(), 0);
}
else if (f.getType() == DisplayType.Percent) {
o.put(f.getName(), 0);
}
else if (f.getType() == DisplayType.Phone) {
o.put(f.getName(), '555-555-1212');
}
else if (f.getType() == DisplayType.String) {
o.put(f.getName(), 'TEST');
}
else if (f.getType() == DisplayType.TextArea) {
o.put(f.getName(), 'TEST');
}
else if (f.getType() == DisplayType.Time) {
o.put(f.getName(), System.now().time());
}
else if (f.getType() == DisplayType.URL) {
o.put(f.getName(), 'http://foo.com');
}
else if (f.getType() == DisplayType.PickList) {
o.put(f.getName(), f.getPicklistValues()[0].getValue());
}
}
}
return o;
}
@isTest static void testTrigger() {
Test.setMock(HttpCalloutMock.class, new TrayWebhookTriggerTest());
SObject o = mock('YourObject');
Test.startTest();
insert o;
update o;
delete o;
Test.stopTest();
System.assertEquals(200, response.getStatusCode());
// CHANGE: the URL below to the URL of your workflow
System.assertEquals('https://c6670ba1-ad45-416e-8489-386c0456cbaa.trayapp.io', request.getEndpoint());
if (request != null) {
Map<String, Object> jsonResponse = (Map<String, Object>) JSON.deserializeUntyped(request.getBody());
System.assertNotEquals(null, jsonResponse.get('userId'));
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment