Challenge-Response Checks a.k.a CRC in Salesforce
CRC is a mechanism by which the event publisher verifies that the registered subscriber owns the URL that is registered (via webhook) for receiving events. For e.g. Twitter's webhook-based APIs provide the challenge-response checks to confirm the ownership of the web app receiving webhook events.
In this article we will see how can we create one such web app - which is nothing but a Apex REST service, and register it to twitter webhook. This is part of the configuration step required for Twitter Salesforce integration article here.
How CRC works
For a CRC of your Apex REST service, twitter will actually make a GET request to your service with a crc_token
parameter. When that request is received, our service needs to build an encrypted response_token
based on the crc_token
parameter and our twitter app's Consumer Secret. The response_token
must be encoded in JSON and returned within three seconds. If successful, a webhook id
will be returned.
Recap of CRC Response requirements
- A base64 encoded HMAC SHA-256 hash created from the
crc_token
and your app Consumer Secret - Valid
response_token
and JSON format. - Latency less than 3 seconds.
- 200 HTTP response code.
In order to implement this we need a simple Apex REST service made public via Guest profile. The class must have two HTTP request methods
- GET - triggered when a CRC request is sent by twitter during webhook registration. This will read the
crc_token
passed as a URL parameter and generate a response as shown below adhering to the response requirements mentioned above. The URL of the service would be of the formhttps://<SalesforceDomainName>/services/apexrest/{Prefix of your Apex REST Service}?crctoken={random characters set by twitter}
.
{
"responsetoken": "sha256=x0mYd8hz2goCTfcNAaMqENy2BFgJJfJOb4PdvTffpwg="
}
- POST - triggered after a successful webhook registration when any webhook event occurs at twitter end.
Sample Apex REST Service
@RestResource(urlMapping='/{prefix}')
global class TwitterSF {
public class myres{
String response_token;
}
@HttpGet
global static void getResponse()
{
RestRequest restReq = RestContext.request;
String crc_token = restReq.params.get('crc_token');
RestContext.response.addHeader('Content-Type','application/json');
system.debug('crc_token'+crc_token);
String secretKey = String.valueOf('{Obtained API secret key}');
String key = 'key';
String crctoken;
Blob data = crypto.generateMac('HmacSHA256',
Blob.valueOf(crc_token),Blob.valueOf(secretKey));
String response = '{'+
'response_token: sha256=' +EncodingUtil.base64Encode(data)+
'}';
myres r = new myres();
r.response_token = 'sha256='+EncodingUtil.base64Encode(data);
RestContext.response.responseBody = Blob.valueOf(JSON.serialize(r));
}
@HttpGet
global static void processResponse()
{
//Logic for processing the event. e.g. create case from the tweet
}
}