Please Note: This is a Proof of Concept integration between ServiceNow and a third party product. This integration is not intended as a complete solution, but rather as a prototype to demonstrate how you could design a similar integration.
We were talking to a prospect the other day, who had an interesting request. Their company already uses Salesforce.com’s Chatter feeds for everything in their company. Every document in salesforce has an associated feed with it. They do not want to move away from Chatter, but they want to introduce ServiceNow into their ITSM strategy. While ServiceNow already features LiveFeed as a way to associate feeds per record or document in the system, the prospect wanted to see the possibility of connecting solely to Salesforce’s chatter feeds for the mean time.
The initial suggestion I had was to integrate the Live Feed feature with Chatter, but they wanted us to demonstrate the capability of doing this without the Live Feed in ServiceNow.
Approach
Salesforce.com allows you configure your instance such that Chatter feeds can be associated with many of their object types. They also provide a good REST-based API for their Chatter feed operations. One thing to note is that their Chatter API does not allow you to create a feed. Instead, you have to create a record that can have a feed associated to it. Once there is a record and associated feed, then the API allows you to perform queries and operations on that feed (such as posting a comment).
Since feeds are required to be connected to some sort of record within Salesforce, I decided to leverage the out of box ServiceNow integration with salesforce.com. This integration sets up a relationship between Salesforce.com’s Case management system and ServiceNow’s incident management system. When cases are created within Salesforce with specific criteria, a corresponding incident is created in ServiceNow.
I decided that once this relationship was established through the existing integration, I would add a “View Chatter Feed” button on the incident form. When the user clicks this button, it should make a Chatter API request for the corresponding feed, popup a GlideDialogWindow object, and populate the feed within that window.
Demo
Salesforce.com Configuration Considerations
By default, Chatter feeds are not associated with case records. So you need to go into the Salesforce instance and configure Chatter feeds to attach themselves to cases.
Also, the Chatter API uses OAuth2 for authentication, which is different than the standard Salesforce.com API that is used by the OOB integration with ServiceNow. Because of this, you have to set up remote API access to the Chatter feed by setting up a “Remote Access” record through App Setup -> Develop -> Remote Access.
Once this Remote Access record is created, the system will provide you with a Consumer Key and Consumer Secret that you must use with your OAuth2 authentication in the REST based API calls to Chatter.
There are a number of OAuth2 methods that the Chatter API supports. For this integration, I was playing under the assumption that not all ServiceNow users would have access to Salesforce.com accounts. Therefore, I chose to use the Username/Password OAuth2 profile and set up the integration to pass in a single set of credentials to Salesforce as an integration user. With this method, you must either append the API Security token to the password, or set up an IP Address trust between Salesforce.com and your IP Address.
REST Calls to Chatter
There are principally three REST based calls that we are leveraging for this Proof of Concept integration.
Authentication
REST Endpoint: https://login.salesforce.com/services/oauth2/token
Method: POST
Function Parameters:
- client_id – the Consumer Key provided by the Remote Access settings in Salesforce.com
- client_secret – the Consumer Secret provided by the Remote Access settings in Salesforce.com
- username – the Salesforce.com username for the user account we are using as the integration point
- password – The Salesforce.com password for the user record
- grant_type – this will be hardcoded to be “password”
Sample Response:
1 2 3 4 5 6 7 | { "id":"https://login.salesforce.com/id/00Di0000000HtBGEA0/005i0000000DufiAAC", "issued_at":"1361510879941", "instance_url":"https://na15.salesforce.com", "signature":"iI9yLDT9M7iyaEJSSVLRIGo6r1a4HdRE4uv3K93ldwc=", "access_token":"00Di0000000HtBG!ARoAQEfTnfPw8PIpZKyH.5kY7nilZZHzCLc3Bt5a7AOximyCSAGrIhsBLkP3xfsxCQd1ddJgqa66UGtRkAYIGgm3yW_xSJMp" } |
Retrieving a Feed
REST Endpoint: https://YOUR_PREFIX.salesforce.com/services/data/v27.0/chatter/feeds/record/CASE_RECORD_ID/feed-items
Method: GET
Headers:
- Authorization : Bearer THE_AUTH_TOKEN_FROM_THE_AUTHENTICATION_RESPONSE
Sample Response:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 | { "items":[ { "parent":{ "name":"00001004", "id":"500i0000000lT22AAE", "motif":{ "largeIconUrl":"/img/icon/cases64.png", "mediumIconUrl":"/img/icon/cases32.png", "smallIconUrl":"/img/icon/cases16.png" }, "url":"/services/data/v27.0/chatter/records/500i0000000lT22AAE", "mySubscription":null, "type":"Case" }, "id":"0D5i00000009vxkCAA", "type":"TextPost", "clientInfo":null, "url":"/services/data/v27.0/chatter/feed-items/0D5i00000009vxkCAA", "createdDate":"2013-02-22T05:34:51.000Z", "body":{ "text":"seventeen bottles of sprite on the wall", "messageSegments":[ { "type":"Text", "text":"seventeen bottles of sprite on the wall" } ] }, "event":false, "visibility":"InternalUsers", "modifiedDate":"2013-02-22T05:34:51.000Z", "photoUrl":"https://c.na15.content.force.com/profilephoto/005/T", "canShare":false, "comments":{ "total":0, "comments":[ ], "nextPageUrl":null, "currentPageUrl":"/services/data/v27.0/chatter/feed-items/0D5i00000009vxkCAA/comments" }, "likes":{ "total":0, "likes":[ ], "nextPageUrl":null, "currentPageUrl":"/services/data/v27.0/chatter/feed-items/0D5i00000009vxkCAA/likes", "previousPageUrl":null }, "likesMessage":null, "isBookmarkedByCurrentUser":false, "isDeleteRestricted":false, "isLikedByCurrentUser":false, "myLike":null, "actor":{ "name":"Mark Masterson", "title":null, "userType":"Internal", "firstName":"Mark", "lastName":"Masterson", "companyName":"HT4.org", "isActive":true, "photo":{ "url":"/services/data/v27.0/chatter/users/005i0000000DufiAAC/photo", "largePhotoUrl":"https://c.na15.content.force.com/profilephoto/005/F", "photoVersionId":null, "smallPhotoUrl":"https://c.na15.content.force.com/profilephoto/005/T", "fullEmailPhotoUrl":"https://na15.salesforce.com/img/userprofile/default_profile_200.png?fromEmail=1", "standardEmailPhotoUrl":"https://na15.salesforce.com/img/userprofile/default_profile_45.png?fromEmail=1" }, "mySubscription":null, "id":"005i0000000DufiAAC", "motif":{ "largeIconUrl":"/img/icon/profile64.png", "mediumIconUrl":"/img/icon/profile32.png", "smallIconUrl":"/img/icon/profile16.png" }, "url":"/services/data/v27.0/chatter/users/005i0000000DufiAAC", "type":"User" }, "attachment":null, "originalFeedItem":null, "originalFeedItemActor":null, "preamble":{ "text":"Mark Masterson", "messageSegments":[ { "motif":{ "largeIconUrl":"/img/icon/profile64.png", "mediumIconUrl":"/img/icon/profile32.png", "smallIconUrl":"/img/icon/profile16.png" }, "reference":{ "id":"005i0000000DufiAAC", "url":"/services/data/v27.0/chatter/users/005i0000000DufiAAC" }, "type":"EntityLink", "text":"Mark Masterson" } ] }, "topics":null } ], "isModifiedToken":null, "nextPageUrl":null, "currentPageUrl":"/services/data/v27.0/chatter/feeds/record/500i0000000lT22AAE/feed-items", "isModifiedUrl":null } |
Posting to a Feed
REST Endpoint: https://YOUR_PREFIX.salesforce.com/services/data/v27.0/chatter/feeds/record/CASE_RECORD_ID/feed-items
Method: GET
Headers:
- Authorization : Bearer THE_AUTH_TOKEN_FROM_THE_AUTHENTICATION_RESPONSE
- Content-Type : application/json
Content Body:
1 2 3 4 5 6 7 8 9 10 11 | { "body" : { "messageSegments" : [ { "type": "Text", "text" : "YOUR_MESSAGE_LINE_GOES_HERE" } ] } } |
Sample Response:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 | { "parent":{ "name":"00001004", "id":"500i0000000lT22AAE", "motif":{ "largeIconUrl":"/img/icon/cases64.png", "mediumIconUrl":"/img/icon/cases32.png", "smallIconUrl":"/img/icon/cases16.png" }, "url":"/services/data/v27.0/chatter/records/500i0000000lT22AAE", "mySubscription":null, "type":"Case" }, "id":"0D5i00000009y6YCAQ", "type":"TextPost", "clientInfo":{ "applicationName":"RestTest1", "applicationUrl":"null" }, "url":"/services/data/v27.0/chatter/feed-items/0D5i00000009y6YCAQ", "createdDate":"2013-02-22T21:38:55.000Z", "body":{ "text":"My first REST based feed post", "messageSegments":[ { "type":"Text", "text":"My first REST based feed post" } ] }, "event":false, "visibility":"InternalUsers", "modifiedDate":"2013-02-22T21:38:55.000Z", "photoUrl":"https://c.na15.content.force.com/profilephoto/005/T", "canShare":false, "comments":{ "total":0, "comments":[ ], "nextPageUrl":null, "currentPageUrl":"/services/data/v27.0/chatter/feed-items/0D5i00000009y6YCAQ/comments" }, "likes":{ "total":0, "likes":[ ], "nextPageUrl":null, "currentPageUrl":"/services/data/v27.0/chatter/feed-items/0D5i00000009y6YCAQ/likes", "previousPageUrl":null }, "likesMessage":null, "isBookmarkedByCurrentUser":false, "isDeleteRestricted":false, "isLikedByCurrentUser":false, "myLike":null, "actor":{ "name":"Service Now", "title":null, "userType":"Internal", "firstName":"Service", "lastName":"Now", "companyName":"HT4.org", "isActive":true, "photo":{ "url":"/services/data/v27.0/chatter/users/005i0000000DufiAAC/photo", "largePhotoUrl":"https://c.na15.content.force.com/profilephoto/005/F", "photoVersionId":null, "smallPhotoUrl":"https://c.na15.content.force.com/profilephoto/005/T", "fullEmailPhotoUrl":"https://na15.salesforce.com/img/userprofile/default_profile_200.png?fromEmail=1", "standardEmailPhotoUrl":"https://na15.salesforce.com/img/userprofile/default_profile_45.png?fromEmail=1" }, "mySubscription":null, "id":"005i0000000DufiAAC", "motif":{ "largeIconUrl":"/img/icon/profile64.png", "mediumIconUrl":"/img/icon/profile32.png", "smallIconUrl":"/img/icon/profile16.png" }, "url":"/services/data/v27.0/chatter/users/005i0000000DufiAAC", "type":"User" }, "attachment":null, "originalFeedItem":null, "originalFeedItemActor":null, "preamble":{ "text":"Service Now", "messageSegments":[ { "motif":{ "largeIconUrl":"/img/icon/profile64.png", "mediumIconUrl":"/img/icon/profile32.png", "smallIconUrl":"/img/icon/profile16.png" }, "reference":{ "id":"005i0000000DufiAAC", "url":"/services/data/v27.0/chatter/users/005i0000000DufiAAC" }, "type":"EntityLink", "text":"Service Now" } ] }, "topics":null } |
The UI Page
A lot of the viewable magic here is held within the UI Page that displays the Chatter Feed. For the most part, I tried to make the feed inherit the styles of ServiceNow’s Live Feed to keep the look and feed consistent.
For the proof of concept, I kept the feed to a basic profile picture, Name, and Message. I implemented posting messages, but no liking, commenting, or following.
My UI Page leverages a couple of script includes that I wrote. That code is available in the update set rather than here in the this article.
Here is the UI Page code:
HTML:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 | <?xml version="1.0" encoding="utf-8" ?> <j:jelly trim="false" xmlns:j="jelly:core" xmlns:g="glide" xmlns:j2="null" xmlns:g2="null"> <LINK href="/styles/LiveFeedIncludes.cssx?v=08-17-2012_1801" rel="stylesheet" type="text/css"/> <g:evaluate object="true"> var chatUtil = new SFDCChatterUtils(); chatUtil.logHigh("Entering SFDC_Chatter UI Page"); var view = "page"; var recordID = RP.getParameterValue("recordID"); if(recordID==""){ var recordID = RP.getWindowProperties().get('recordID'); view="dialog"; } chatUtil.logHigh("recordID: " + recordID); if(recordID){ chatUtil.logHigh("view: " + view); var feed = chatUtil.getRecordFeed(recordID); var feedSize = feed.items.length; var itemCounter = 0; chatUtil.logHigh("FeedSize: "+feedSize); if(chatUtil.verbosity == "High"){ //JSUtil.logObject(feed); } } </g:evaluate> <j:if test="${recordID!='' && feedSize!=null}"> <div style='padding:20px;'> <div id="live_feed_input_div" style=""> <form id="live_feed_form" name="live_feed_form" action='/SFDC_Chatter.do' method='POST'> <div style="padding-right: 4px;"> <textarea id="live_feed_input" name="message" class="live_feed_input" data-prompt="What are you working on today?" style="resize: none; overflow-y: hidden; height: 28px; color: rgb(102, 102, 102); opacity: 0.9;"></textarea> <textarea class="live_feed_input" data-prompt="What are you working on today?" style="color: rgb(102, 102, 102); opacity: 0.9; resize: none; overflow-y: hidden; position: absolute; visibility: hidden; top: 0px; left: -9999px; width: 476px;" tabindex="-1"></textarea> </div> <table style="width: 100%; padding: 0; position:relative; margin-top: 5px;"> <tbody> <tr> <td style="text-align: right" width="90%"> <input type='button' onclick="addComment('${recordID}')" value="Update"/> </td> </tr> </tbody> </table> </form> </div> <j:while test="${itemCounter!=feedSize}"> <j:set var="jvar_actor" value="${feed.items[itemCounter].actor.name}"/> <j:set var="jvar_createdDate" value="${feed.items[itemCounter].createdDate}"/> <j:set var="jvar_message" value="${feed.items[itemCounter].body.text}"/> <j:set var="jvar_avatar" value="${feed.items[itemCounter].photoUrl}"/> <div class="live_feed_message "> <div class="live_feed_avatar"> <div class="live_feed_member_avatar live_feed_avatar_profile_img live_feed_follow_buttons"> <div class="live_feed_avatar_img" style='background-image:url("/images/profile/buddy_default.pngx");padding-top:0px;padding-bottom:0px;'> <img class="live_feed_avatar_img" src="${jvar_avatar}" style='width:48px;height:48px;'/> </div> </div> </div> <div class="live_feed_message_text"> <span class="live_feed_by">${jvar_actor}</span>: ${HTML:jvar_message} </div> <div class="live_feed_message_date"> <span title="${jvar_createdDate}">${jvar_createdDate}</span> </div> <j:set var="itemCounter" value="${itemCounter+=1}"/> </div> </j:while> </div> </j:if> </j:jelly> |
Client Script:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | function addComment(recordID){ //Do an ajax call to post the Message then... var message = document.getElementById("live_feed_input"); var name = g_user.firstName+" "+g_user.lastName; var ga = new GlideAjax('SFDCChatterAjax'); ga.addParam('sysparm_name', "ajaxPostFeedItem"); ga.addParam('sysparm_recordID',recordID); ga.addParam('sysparm_message',"("+name+") "+message.value); ga.getXMLWait(); //alert("message: "+message.value); //Refresh the dialog GlideDialogWindow.get().render(); } //Allow the feed to automatically display new feed entries from external sources setTimeout(function(){ var textarea = document.getElementById("live_feed_input"); if(!textarea.value || textarea.value==" "){ GlideDialogWindow.get().render(); } }, 5000); |
Setup and Configuration
If you want to take this integration out for a spin, you just need to follow these steps to get it up and running.
- Enable the Salesforce.com Integration plugin in your ServiceNow instance. You many need to request that Support enable it for you if you don’t see it listed in the Plugins list.
- Configure and test the Salesforce.com integration to ensure it is functioning properly. Use the wiki documentation as a guide: Salesforce.com Integration Documentation.
- Install the Update Set included at the end of this article.
- Set up a Remote Access account in Salesforce.com for your instance (explained earlier in this article).
- Set up Chatter feed to track the Case Management data in Salesforce.com (explained earlier in this article).
- (optional) Set up Salesforce to trust your instance IP address (explained earlier in this article).
- Go to the ServiceNow application for “Integration – Salesforce.com” and configure the additional properties created by this update set: Sales Force Instance Prefix, Salesforce Consumer Key, Salesforce API Consumer Secret, Chatter Log Verbosity Level.
Hats off to a fantastic PoC John! Great work.
Simply love this!
Hi James, It looks cool. trying to get this implemented. But having some queries.
Ho can I enable to Service plugin of Salesforce.
Can we do this in Developer SFDC Organisation.
Your help can be very valuable for me.
Is salesforce integration plugin feature is working in demosandbox of ServiceNow.
Thanks
Pravin
Pravin,
There is actually no live demo of this since we do not have a dedicated salesforce instance. We used a 30-day trial to build out the integration proof-of-concept.
Hi,
Integration between ServiceNow and Salesforce Chatter is possible using Informatica Cloud. For more information over ServiceNow Connector, please visit at-http://www.mansasys.com/servicenow-connector.
For any further query, please reach at info(at)mansasys(dot)com
Thanks,
Akash
hi John,
How can we have an integration with Chatter with ServiceNow Live Feed ?
Hi John,
Is there any way we can update incident record in the SNOW, and Case object in the Salesforce gets updated?