Slack is a team collaboration application that has gained a significant amount of momentum over the last 2 years. One of the things that sets Slack apart is the simple and powerful way it can integrate with external applications (Google Drive, Twitter, Trello, or any other app). Using Incoming Webhooks, external apps can automatically post messages to Slack channels when specific events under their control occur. Using Slash Commands, users can query or update information in external apps from within the Slack UI. You can even set up Bots to listen to Slack conversations and automatically take actions in response to specific patterns. These integrations allow you to turn Slack into a centralized real-time messaging hub.
In this article, I demonstrate a simple integration scenario between Slack and Salesforce. As described above, the Slack and Salesforce integration can take different forms:
- Salesforce to Slack. For example, automatically post messages to a Slack channel when specific events happen inside Salesforce (Account created, Opportunity status change, etc.)
- Slack to Salesforce. For example, use a Slash Command to quickly access or update Salesforce data from within the Slack UI.
In this post, I’ll focus on the “Salesforce to Slack” scenario, leaving “Slack to Salesforce” for another post (now available here). As an example, we will create a custom integration that automatically posts a message to the #general Slack channel when the status of an opportunity changes in Salesforce.
Step 1: Create a Webhook
A Webhook is an endpoint that an external application (in this case, Salesforce) can use to POST messages to a Slack channel. The POST’s JSON payload includes the message text and some other options.
Here is my Webhook configuration for the Salesforce Opportunities integration:
All you have to do is specify the Slack Channel to post to: I chose the #general channel for this example. The Webhook URL is automatically generated. You can optionally provide a custom username for the integration (I named it “Salesforce Opportunities”) and upload a custom logo.
Step 2: Create the SlackOpportunityPublisher Apex Class
The second step is to create a simple Apex class that is capable of posting a message to the Webhook URL. The message includes the Opportunity details (Opportunity name and the new stage).
In the Developer Console, create a new Apex class called SlackOpportunityPublisher defined as follows:
public with sharing class SlackOpportunityPublisher { private static final String slackURL = 'YOUR_WEBHOOK_URL'; public class Oppty { @InvocableVariable(label='Opportunity Name') public String opptyName; @InvocableVariable(label='Stage') public String stage; } @InvocableMethod(label='Post to Slack') public static void postToSlack(List<Oppty> oppties) { Oppty o = oppties[0]; // If bulk, only post first to avoid overloading Slack channel Map<String,Object> msg = new Map<String,Object>(); msg.put('text', 'The following opportunity has changed:\n' + o.opptyName + '\nNew Stage: *' + o.stage + '*'); msg.put('mrkdwn', true); String body = JSON.serialize(msg); System.enqueueJob(new QueueableSlackCall(slackURL, 'POST', body)); } public class QueueableSlackCall implements System.Queueable, Database.AllowsCallouts { private final String url; private final String method; private final String body; public QueueableSlackCall(String url, String method, String body) { this.url = url; this.method = method; this.body = body; } public void execute(System.QueueableContext ctx) { HttpRequest req = new HttpRequest(); req.setEndpoint(url); req.setMethod(method); req.setBody(body); Http http = new Http(); HttpResponse res = http.send(req); } } }
This class knows how to post a message to Slack, but doesn’t know when to do it. We’ll take care of that in Step 3.
Step 3: Create a Simple Process in ProcessBuilder
The last step is to define a simple process that calls SlackOpportunityPublisher when the Stage of an opportunity is changed. Watch the video below to see how to easily create the process.
HI. Thanks for this tutorial. It was very helpful. I just wanted to know if using the process builder for posting webhooks has any advantages over using a trigger for this purpose.
good of nice blogees
Hi Christophe and many thanks for that tricks….it works very well on my sandbox but as I’m not a developer at all, I don’t know how to write a test class for it…When I want to deploy it on production it says that my code coverage is 0%….Can you help me on that?
This tutorial was super helpful, thank you!
I had to add https://hooks.slack.com as a Remote Site under Setup–>Security–>Remote Site Settings
Just in case that helps someone else out.
Hi Bryony
Could you help me with the test class to write in order to put it on production? It works well on my sandbox but i’m not able to deploy it on production because my code is not covered. As I am not a developer it s quite hard for me to handle that.
Thanks in advance
Romain
Need the same… :(
Needs the same help please
@isTest
private class SlackOpportunityPublisherTest{
static testMethod void testPostToSlack() {
SlackOpportunityPublisher.Id pubTest = new SlackOpportunityPublisher.Id();
pubTest.opptyName = ‘Test opportunity’;
pubTest.type = ‘Test Type’;
pubTest.stage = ‘Test stage’;
pubtest.probability = ‘Test probability’;
pubTest.first = ‘Test First’;
pubTest.last = ‘Test Last’;
pubTest.close = ‘Test Close’;
List theList = new List();
theList.add(pubTest);
SlackOpportunityClosePublisher.postToSlack(theList);
System.assertEquals(theList, theList); // Can’t really test this, just put something that is true
}
}
YES! Thanks Bryony, this was my issue :D
Hi Romain —
Apologies, I am just learning Apex myself and don’t know offhand how to write the test class, but if I get it done I’ll try to remember to post here.
Hey, thanks! really helped!
Now I need to deploy it to production and need to write tests for it. Do you have any test code that covers 75% of it?
Thanks a lot
Natalie
i second the need for some good code coverage.
Residential property in Pune in 2016, much the same as the earlier years, is achieving new statures. Numerous corporates and commercial ventures are moving to the city yet predominantly the IT division is the main thrust behind the speeding up of costs of property available to be purchased in Pune. In spite of the fact that the Indian economy is gazing upward, the scale at which the IT business is developing in the city is mind boggling and impossible. Call us- 0124-4370612
Real Estate Developers In Pune
Whilst this post is great to get started I needed to change a couple of things to be able to actually deploy this Apex class on our SF production instance:
– Add test class
– Skip the http call in tests as these are not allowed in tests
My final Apex class:
public with sharing class SlackOpportunityPublisher {
private static final String slackURL = ‘YOUR_WEBHOOK_URL’;
public class Oppty {
@InvocableVariable(label=’Opportunity Name’)
public String opptyName;
@InvocableVariable(label=’Stage’)
public String stage;
}
@InvocableMethod(label=’Post to Slack’)
public static void postToSlack(List oppties) {
Oppty o = oppties[0]; // If bulk, only post first to avoid overloading Slack channel
Map msg = new Map();
msg.put(‘text’, ‘The following opportunity has changed:n’ + o.opptyName + ‘nNew Stage: *’ + o.stage + ‘*’);
msg.put(‘mrkdwn’, true);
String body = JSON.serialize(msg);
System.enqueueJob(new QueueableSlackCall(slackURL, ‘POST’, body));
}
public class QueueableSlackCall implements System.Queueable, Database.AllowsCallouts {
private final String url;
private final String method;
private final String body;
public QueueableSlackCall(String url, String method, String body) {
this.url = url;
this.method = method;
this.body = body;
}
public void execute(System.QueueableContext ctx) {
HttpRequest req = new HttpRequest();
req.setEndpoint(url);
req.setMethod(method);
req.setBody(body);
Http http = new Http();
if (!Test.isRunningTest()) { // HTTP callout is not allowed in tests apparently…
HttpResponse res = http.send(req);
}
}
}
}
My accompanying test class:
@isTest
private class SlackOpportunityPublisherTest {
static testMethod void testPostToSlack() {
SlackOpportunityPublisher.Oppty opt = new SlackOpportunityPublisher.Oppty();
opt.opptyName = ‘Unit Test Opt’;
opt.stage = ‘Unit Test Stage’;
List lis = new List();
lis.add(opt);
SlackOpportunityClosePublisher.postToSlack(lis);
System.assertEquals(lis, lis); // Can’t really test this, just put something that is true
}
}
As others have mentioned don’t forget to add the Slack Webhooks domain as remote site in the security settings. Then everything works great.
Hi Garnele, thanks for providing your version and test class. These worked perfectly for our org. I was asked to add opp amount and a direct link to the opportunity record (captured via a custom field called RecordURL) to the posted slack message. I came up with the following, but no success. Any chance you can help troubleshoot?
public with sharing class SlackOpportunityPublisher {
private static final String slackURL = ‘https://hooks.slack.com/services/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’;
public class Oppty {
@InvocableVariable(label=’Opportunity Name’)
public String opptyName;
@InvocableVariable(label=’Amount’)
public String opptyAmount;
@InvocableVariable(label=’RecordURL’)
public String opptyURL;
@InvocableVariable(label=’Stage’)
public String stage;
}
@InvocableMethod(label=’Post to Slack’)
public static void postToSlack(List oppties) {
Oppty o = oppties[0]; // If bulk, only post first to avoid overloading Slack channel
Map msg = new Map();
msg.put(‘text’, ‘Gong! Boom! ULTRA BOOM!! Label Insight just won: *’ + o.opptyName + ‘*’+ ‘nAmount: ‘+ o.opptyAmount+ ‘nOpportunity Record: ‘+ o.opptyURL);
msg.put(‘mrkdwn’, true);
String body = JSON.serialize(msg);
System.enqueueJob(new QueueableSlackCall(slackURL, ‘POST’, body));
}
public class QueueableSlackCall implements System.Queueable, Database.AllowsCallouts {
private final String url;
private final String method;
private final String body;
public QueueableSlackCall(String url, String method, String body) {
this.url = url;
this.method = method;
this.body = body;
}
public void execute(System.QueueableContext ctx) {
HttpRequest req = new HttpRequest();
req.setEndpoint(url);
req.setMethod(method);
req.setBody(body);
Http http = new Http();
if (!Test.isRunningTest()) { // HTTP callout is not allowed in tests apparently…
HttpResponse res = http.send(req);
}
}
}
}
Did you ever find a solution to this?
Hi Joanna,
I can help you with this. Add an @InvocableVariable to your:
public class Oppty{
@InvocableVariable(label=’Amount’)
public String amount;
@InvocableVariable(label=’Link’)
public String id;
}
Change your opptyURL to ‘http://myinstance.salesforce.com/’ + o.id);
Sample:
‘nLink: ‘ + ‘https://testappbuilder-dev-ed.my.salesforce.com/’ + o.id);
Garnele! You are brilliant! I appreciate this. Not a great item to leave out from a instructional.
From where are you referencing this SlackOpportunityClosePublisher from?
I am getting this error: Method does not exist or incorrect signature: void pushToSlack(List) from the type SlackOpportunityNotify
Nevermind. I have resolved this issue
how, i’m stuck here now?
Hi @shaunguido:disqus, Check your Apex Class now. If you are following Garenele’s code above. Make sure you are using IsRunningTest()
Example:
if (!Test.isRunningTest()) { // HTTP callout is not allowed in tests apparently…
HttpResponse res = http.send(req);
}
Further you can follow his @isTest Class too.
@disqus_s1TZE3lIX7:disqus weird .. it looks like everything is the same, but I get a different error, in the test class : “Error: Compile Error: Variable does not exist: SlackOpportunityClosePublisher at line 13 column 1”
@shaunguido:disqus Use your Apex Class name, there seems to be a typo in the above code.
Example: public with sharing class SlackOpportunityPublisher(class name)
@disqus_s1TZE3lIX7:disqus yeah I tried that and got a little further .. now I can’t seem to deploy .. I tried them both together to validate .. gets errors. argh. (thanks for trying to help)
You will get there. Look if there are any typo writing variable names in test class. I was facing that issue.
Appreciate the additional color on the Test Class. I used your example code and ran into an issue where the test is never successful. What do you suggest is a ‘true’ statement to add where you commented:
System.assertEquals(lis, lis); // Can’t really test this, just put something that is true
What’s the secret to doing this with another object? I have a custom object and I want to post to slack when a new one is created. Any ideas?
These are great! Thanks for posting this.
hiiiiiiiiiiiiiiii
visit these – http://www.pokerlauncher.com
This is great, thanks. We were just told that the only way to do this was to double our salesforce spend and move up to the Enterprise edition. Pretty disappointing news. Anyone know a way around this?
How to navigate to “Create Webhook ” window?
Thank you for posting this Christopher and to Garnele for the Test Class! I am still getting: ‘Methods defined as TestMethod do not support Web service callouts’. My test is exactly like Garnele’s but it fails. Anyone have any suggestions? This is the first Apex class I am creating. Thank you in advance!
Hi Ada, do you have any response for that ? I have the same context :( Thanks !!!! :)
Can anybody please help to create an Apex Class for posting cases (case number, subject, severity) to Slack? Thank you!
Hello Tatyana – where you able to create the apex class for cases? I am trying to do the same. Thanks
Hi Priya, unfortunately, i wasn’t. I don’t have the skills and was hoping somebody here would help.
Priya & Tatyana,
See Code Below:
public with sharing class SlackPrimeCasePublisher {
private static final String slackURL = Web_Hook_URL
public class primeCase {
@InvocableVariable(label='Case Number')
public String CaseNumber;
@InvocableVariable(label='Subject')
public String Subject;
@InvocableVariable(label='Case Id')
public ID caseId;
}
@InvocableMethod(label='Post to Slack')
public static void postToSlack(List primeCases) {
primeCase o = primeCases[0];
Map msg = new Map();
msg.put('text', _message_here_);
msg.put('mrkdwn', true);
String body = JSON.serialize(msg); //turns message into a JSON
System.enqueueJob(new QueueableSlackCall(slackURL, 'POST', body));//makes a call to webhook, with HTTP request
}
public class QueueableSlackCall implements System.Queueable, Database.AllowsCallouts {
private final String url;
private final String method;
private final String body;
public QueueableSlackCall(String url, String method, String body) {
this.url = url;
this.method = method;
this.body = body;
}
public void execute(System.QueueableContext ctx) {
HttpRequest req = new HttpRequest();
req.setEndpoint(url);
req.setMethod(method);
req.setBody(body);
Http http = new Http();
if(!Test.isRunningTest()){
HttpResponse res = http.send(req);
}
}
}
}
don’t forget to add the web hook to your remote site under security.
don’t forget to map/add variables to your case Class (In this instance, mine is called primeCase)
What am i doing wrong Variable does not exist: SlackOpportunityClosePublisher
public class SlackOpportunityPublisher {
private static final String slackURL = ‘https://hooks.slack.com/services/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’;
public class Id {
@InvocableVariable(label=’Opportunity Name’)
public String opptyName;
@InvocableVariable(label=’Type’)
public String Type;
@InvocableVariable(label=’Stage’)
public String stage;
@InvocableVariable(label=’Probability’)
public String probability;
@InvocableVariable(label=’First’)
public string first;
@InvocableVariable(label=’Last’)
public string last;
@InvocableVariable(label=’Close’)
public string close;
}
@InvocableMethod(label=’Post to Slack’)
public static void postToSlack(List opportunityId) {
Id oppId = opportunityId[0]; // If bulk, only post first to avoid overloading Slack channel
Opportunity opportunity = [SELECT Name, Type, StageName, Probability, Owner.FirstName, Owner.LastName, CloseDate from Opportunity WHERE id=:OPPid];
Map msg = new Map();
msg.put(‘text’, ‘The following opportunity has changed:n’ + ‘Account Name And Product: ‘ + Opportunity.Name + ‘nType: ‘ + opportunity.Type + ‘nNew Stage: ‘ + opportunity.StageName + ‘nSales Person: ‘ + opportunity.Owner.FirstName + ‘ ‘ + opportunity.Owner.LastName + ‘nProbability: ‘ + opportunity.Probability + ‘%’ + ‘nForcasted Close Date: ‘ + opportunity.CloseDate);
msg.put(‘mrkdwn’, true);
String body = JSON.serialize(msg);
System.enqueueJob(new QueueableSlackCall(slackURL, ‘POST’, body));
}
public class QueueableSlackCall implements System.Queueable, Database.AllowsCallouts {
private final String url;
private final String method;
private final String body;
public QueueableSlackCall(String url, String method, String body) {
this.url = url;
this.method = method;
this.body = body;
}
public void execute(System.QueueableContext ctx) {
HttpRequest req = new HttpRequest();
req.setEndpoint(url);
req.setMethod(method);
req.setBody(body);
Http http = new Http();
if (!Test.isRunningTest()) { // HTTP callout is not allowed in tests apparently…
HttpResponse res = http.send(req);
}
}
}
}
@isTest
private class SlackOpportunityPublisherTest{
static testMethod void testPostToSlack() {
SlackOpportunityPublisher.Id pubTest = new SlackOpportunityPublisher.Id();
pubTest.opptyName = ‘Test opportunity’;
pubTest.type = ‘Test Type’;
pubTest.stage = ‘Test stage’;
pubtest.probability = ‘Test probability’;
pubTest.first = ‘Test First’;
pubTest.last = ‘Test Last’;
pubTest.close = ‘Test Close’;
List theList = new List();
theList.add(pubTest);
SlackOpportunityClosePublisher.postToSlack(theList);
System.assertEquals(theList, theList); // Can’t really test this, just put something that is true
}
}
when i change SlackOpportunityClosePublisher.postToSlack(theList); to SlackOpportunityPublisher.postToSlack(theList); i get Method does not exist or incorrect signature: void postToSlack(List) from the type SlackOpportunityPublisher
Amazing sites and great work. Pretty nice information. it has a better understanding. thanks for spending time on it.
Hadoop Training Institute in Noida
Best Hadoop Training in Noida
Slack Plugin is a networked messaging that is perfect with Windows, Mac, iOS, and Android. It enables you to make a gathering or group where you can impart through sound video, share archives and oversee ventures. In spite of the fact that the instrument is free, there are two premium alternatives with additional highlights. The standard record costs $8 every month for every individual paying yearly and offers boundless application reconciliations and boundless message seeks. Capacity limit is up to 10 GB for every individual transfer, bunch calls, visitor accounts when you redistribute, and need bolster. Some essential component that has been under looked is making of various groups for one, which makes it an undertaking the board apparatus
https://bit.ly/2I2e5Hc
Great post and I have really enjoyed creating this in my sandbox today – the only thing i cant get to work is the URL – my line of code is:
msg.put(‘text’, ‘The following opportunity has been marked as closed won:n’ + o.opptyName + ‘nFinal Stage: *’ + o.stage + ‘*’+ ‘nTCV $: *’ + o.amount + ‘*’+ ‘nCS Owner: *’ + o.csowner + ‘*’+ ‘nOpp Owner: *’ + o.ownerid + ‘*’+’nOpp URL Link:’ + ‘https://debbie–debbie.cs78.my.salesforce.com/+o.Id’+’nAccount Name: *’ + o.AccountId + ‘*’);
however on the slack post this is coming up as:
Opp URL Link:https://debbie–debbie.cs78.my.salesforce.com/+o.Id
Great work
To crack scrum master interview : Scrum Master Interview Questions
Thanks for sharing this informative content , Great work
Leanpitch provides online training in Enterprise agile coaching during this lockdown period everyone can use it wisely.
Enterprise Agile Coaching
Thanks for sharing this informative content , Great work
Leanpitch provides online training in Devops coaching during this lockdown period everyone can use it wisely.
Devops Online Training
Thanks for sharing this informative content .,
Leanpitch provides online training in Scrum Master during this lockdown period everyone can use it wisely.
Join Leanpitch 2 Days CSM Certification Workshop in different cities.
CSM certification online
CSM online
CSM online certification
Scrum master certification online
CSM online training
I have this code running for over 4 years now. But sometimes I get error messages when there are many queueable jobs running in the background and gets executed in single transaction.
This is the error message: An Apex error occurred: System.LimitException: Too many queueable jobs added to the queue: 51
how to handle this?