I'm new here and I'm hoping to get some guidance on the proper way to write this in Apex. I have a webhook coming from Hubspot and I'm receiving it successfully in Salesforce using an apex class exposed as a REST resource. My problem starts when I add a request signature in the header and then validate that in apex.
Hubspot provided examples on how to validating their v2 request signature in Python, Ruby and Node JS. This is the summary:
To verify this signature, perform the following steps:
- Create a string that concatenates together the following: Client secret + http method + URI + request body (if present)
- Create a SHA-256 hash of the resulting string.
- Compare the hash value to the signature.
- If they're equal then this request has passed validation. If these values do not match, then this request may have been tampered with in-transit or someone may be spoofing requests to your endpoint.
Notes:
- The URI used to build the source string must exactly match the original request, including the protocol. If you're having trouble
validating the signature, ensure that any query parameters are in the exact same order they were listed in the original request.- The source string should be UTF-8 encoded before calculating the SHA-256 hash.
Example for a request with a body:
- Client secret : yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy
- HTTP Method: POST
- URI: https://www.example.com/webhook_uri
- Request body: {"example_field":"example_value"}
Therefore, the source string would be: yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyyPOSThttps://www.example.com/webhook_uri{"example_field":"example_value"}
And the resulting hash would be: 9569219f8ba981ffa6f6f16aa0f48637d35d728c7e4d93d0d52efaa512af7900
I tried out a few code snippets was able to use anonymous apex to get the intended result in their example:
String client_secret = 'yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy';
String http_method = 'POST';
String http_uri = 'https://www.example.com/webhook_uri';
String request_body = '{"example_field":"example_value"}';
String source_string = client_secret+http_method+http_uri+request_body;
Blob encrypted = Crypto.generateDigest('SHA-256', Blob.valueOf(source_string);
String myhex = EncodingUtil.convertToHex(encrypted);
System.debug(myhex);
9569219f8ba981ffa6f6f16aa0f48637d35d728c7e4d93d0d52efaa512af7900 <<- result
But when I try to use the same concept with the actual RestRequest, I can't seem to get the signature and my resulting hash to match.
@RestResource(urlMapping='/demo/*')
global with sharing class test2 {
@HttpPost
global static void handleNotification(){
RestRequest request = RestContext.request;
RestResponse response = RestContext.response;
//get signature from the header
String signature = request.headers.get('X-HubSpot-Signature-v3');
//generate my source string
String client_secret = 'XXXXXXX';
String http_method = 'POST';
String http_uri = 'https://myorg.sandbox.my.salesforce-sites.com/services/apexrest/demo';
String requestbody = request.requestBody.toString();
String source_string = client_secret+http_method+http_uri+requestbody;
//start encoding
Blob encrypted = Crypto.generateDigest('SHA-256', Blob.valueOf(source_string));
String myHex = EncodingUtil.convertToHex(encrypted);
RestContext.response.statusCode = myHex == signature ? 200 : 401;
//I always get 401
}
}
This is all new and fun but I've been stuck here for the better part of the day and in need of a nudge towards the right direction.