[SalesForce] HTTP 400 Bad Request response while testing Thomson Reuters World Check One API

I tried to see the request header values before it is send to external webserver. But Httprequest.getbody() methods returns empty. I tried this code below. Also, I have tried to convert the java code which has been given as an example for the POST request. Please see the java code also given below the apex code.

public with sharing class testingscenario
{
    public static String generateAuthHeader(String dataToSign)
    {
        String algorithmName = 'HmacSHA256';
        Blob hmacData = Crypto.generateMac(algorithmName, Blob.valueOf(dataToSign), Blob.valueOf('**********************************'));
        return EncodingUtil.base64Encode(hmacData);
    }
    @Future(callout = true)
    public static void getvalues(String JsonString)
    {

        try {
        String gatewayurl = '/v1/';
        String gatewayhost = 'somewebsite.com';
        String apikey = '**************************';
        String apisecret = '**************************************';

        String contentType = 'application/json';
        String ND=Datetime.now().formatGMT('EEE, dd MMM yyyy HH:mm:ss z');

        //String jsonBody =  '{\"secondaryFields\":[],\"entityType\":\"INDIVIDUAL\",\"customFields\":[],\"groupId\":\"418f28a7-b9c9-4ae4-8530-819c61b1ca6c\",\"providerTypes\":[\"WATCHLIST\"],\"name\":\"george w bush\"}';
          String jsonBody =  '{ \"secondaryFields\": [], \"entityType": \"INDIVIDUAL\", \"customFields\": [], "groupId\":\"*********************\", \"providerTypes\": [ \"WATCHLIST\" ], \"name\": \"putin\" }';        

        JSONGenerator gen = JSON.createGenerator(true);

        List<String> str1 = new List<String>();
        List<String> str2 = new List<String>();
        str2.add('WATCHLIST');


        // Write data to the JSON string.
        gen.writeStartObject();
        gen.writeObjectField('secondaryFields', str1);
        gen.writeStringField('entityType', 'INDIVIDUAL');
        gen.writeObjectField('customFields',str1);
        gen.writeStringField('groupId', '***********************');
        gen.writeObjectField('ProviderTypes', str2);
        gen.writeStringField('name', 'george');

        gen.writeEndObject();

        // Get the JSON string.
        String pretty = gen.getAsString();
        System.debug('json string:>>>>>>>>>'+pretty );
        System.debug('json body:>>>>>>>>>'+jsonbody);
        String content = JSON.serialize(jsonbody);
        Integer Contentlength=192; //pretty.length();
        System.debug('Contentlengthhhhhh'+Contentlength);


/*
        String dataToSign = '(request-target): post' + gatewayurl+ 'cases\n' + 
                'host: ' + gatewayhost + '\n' +
                'date: ' + ND +'\n'+ 
                'content-type: '+contentType +'\n' + 
                'content-length: '+ Contentlength + '\n' + 
                content;
*/      

        String dataToSign2 = '(request-target): post ' + gatewayurl + 'cases\n' +
        'host: ' + gatewayhost  + '\n' +
        'date: ' + ND + '\n' +
        'content-type: ' + contentType  +'\n' + 
        'content-length: ' + contentLength + '\n' + 
        content;                

        String dataToSign1 = '(request-target): post /v1/cases host: somewebsite.com date: Fri, 22 Sep 2017 22:13:50 GMT content-type: application/json content-length: 192 { \"secondaryFields\": [], \"entityType\": \"INDIVIDUAL\", \"customFields\": [], \"groupId\":\"************************\", \"providerTypes\": [ \"WATCHLIST\" ], \"name\": \"putin\" }';

        String hmac = generateAuthHeader(dataToSign1);

        String authorisation = 'Signature keyId=\"' + apikey + '\",algorithm=\"hmac-sha256\",headers=\"(request-target) host date content-type content-length\",signature=\"' + hmac + '\"';
        //String authorisation = 'Signature keyId=\"**********************\",algorithm=\"hmac-sha256\",headers=\"(request-target) host date content-type content-length\",signature=\"***********************"';

        System.debug('authorisation Result'+authorisation);
        System.debug('dataToSign Result'+dataToSign1 );
        System.debug('hmac Result'+hmac);




        HttpRequest request = new HttpRequest();

        request.setEndpoint('https://somewebsite.com/v1/cases');
        request.setMethod('POST');
        request.setTimeout(120000);
        request.setHeader('Authorization',authorisation);
        request.setHeader('Date',ND);
        request.setHeader('Cache-Control', 'no-cache');
        request.setHeader('Content-Type',  'application/json');
        request.setHeader('Content-Length', String.valueOf(contentlength));

        System.debug('request body>>>>>>'+request.getBody());
        System.debug('request body Document>>>>>>'+request.getBodyDocument());

        Http http = new Http();
        HTTPResponse res = http.send(request);
        System.debug('response>>>>>>'+res);
        } catch(exception ex) {
            system.debug(ex.getmessage());
        }



    }
}

Java Code for POST Request:

import java.util.*;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

import com.fasterxml.jackson.databind.*;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;


import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
//import org.apache.commons.logging.*;
import org.json.*;

public class PostRequest {

    public static String generateAuthHeader(String dataToSign, String secret)
   {      
    String hash = "";
      try { 

          Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
          SecretKeySpec secret_key = new SecretKeySpec(secret.getBytes(), "HmacSHA256");
          sha256_HMAC.init(secret_key);
          hash = Base64.encodeBase64String(sha256_HMAC.doFinal(dataToSign.getBytes()));
      }
      catch (Exception e){
          System.out.println("Error");
      }
        return(hash);
   }




       // A simple request to create a Case using Http Post
        public static void main(String[] args) throws Exception {
            CloseableHttpClient httpclient = HttpClients.createDefault();
            try {
                Date now = new Date();

                //format for date string Mon, 27 Mar 2017 15:19:36 GMT 
                DateFormat df = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z");
                df.setTimeZone(TimeZone.getTimeZone("GMT"));

                String date = df.format(now);
                String gatewayurl = "/v1/";
                String gatewayhost = "someapiwebsite.com";
                String apikey = "**************************************";
                String apisecret = "**************************************";
                String groupid = "**************************************";

                //create a JSON string 
                String jsonBody =  "{\"secondaryFields\":[],\"entityType\":\"INDIVIDUAL\",\"customFields\":[],\"groupId\":\"**************************************\",\"providerTypes\":[\"WATCHLIST\"],\"name\":\"george w bush\"}";

                // create a JSON object from the JSON string
                JSONObject jo = new JSONObject(jsonBody);

                //System.out.println(jo.toString());

                String jlen = String.valueOf(jo.toString().length());
                String dataToSign = "(request-target): post " + gatewayurl + "cases\n" +
                        "host: " + gatewayhost + "\n" +
                        "date: " + date + "\n" +
                        "content-type: " +  "application/json" +"\n" + 
                        "content-length: " + jlen + "\n" + 
                        jo;

                String hmac = generateAuthHeader(dataToSign, apisecret);
                String authorisation = "Signature keyId=\"" + apikey  + "\",algorithm=\"hmac-sha256\",headers=\"(request-target) host date content-type content-length\",signature=\"" + hmac + "\"";

                System.out.println(jlen);
                System.out.println(dataToSign);
                //System.out.println(hmac);
                System.out.println(authorisation);
                String msg = jo.toString();

                HttpPost httpPost = new HttpPost("https://someapiwebsite.com/v1/cases");

                HttpEntity entity = new StringEntity(msg);
                httpPost.setEntity(entity);

                httpPost.addHeader("Date", date);
                httpPost.addHeader("Cache-Control", "no-cache");         
                httpPost.addHeader("Content-Type",  "application/json" );
                httpPost.addHeader("Authorization", authorisation);        

                // send the POST request
                CloseableHttpResponse response1 = httpclient.execute(httpPost);

                try {

                    HttpEntity entity1 = response1.getEntity();
                    System.out.println(response1.getStatusLine());

                    String json = EntityUtils.toString(response1.getEntity());
                    //System.out.println(entity1);
                    // json string returned
                    System.out.println(json);
                    ObjectMapper mapper = new ObjectMapper();

                    // printout in Pretty format
                    Object jsonObj = mapper.readValue(json, Object.class);
                    String indented = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonObj);
                    System.out.println(indented);

                    EntityUtils.consume(entity1);

                } 
                finally {
                    response1.close();
                }


            } finally {
                httpclient.close();
            }
        }

    }

Best Answer

If you don't control the target website and can't access logs there - you could try sending the request to a debug logging endpoint. I tend to do it when I'm building new SOAP / REST integrations. When I hit a problem, I debug log the resulting request Salesforce has produced, load it up in soapUI / Chrome REST client and keep hacking at the generated message until I get something meaningful from the target system...

If you don't have your own "request sink" that would log anything you want, you can use https://requestb.in/ It's very simple to use and for your code it means only 2 things:

  • adding requestb.in to Remote Site Settings
  • commenting out the original request.setEndpoint()

As to what's actually wrong - can be anything. Maybe your content-type header is not something the target application expects. Maybe the message signing algorithm is not implemented the way they expect. That's why it's a good idea to send your stuff to such "request sink" and when all else fails - contact target app's developer for assistance, showing him/her the raw output produced by SF.

(No, I'm not affiliated with request bin, soapUI and any other tool like that)


Edit

Here's a working Salesforce implementation of Thomson Reuter's World-Check One API based on their sample Java program.

public with sharing class Stack193260 {

    static final String gatewayurl = '/v1/';
    static final String gatewayhost = 'rms-world-check-one-api-pilot.thomsonreuters.com';
    static final String endpoint = 'https://rms-world-check-one-api-pilot.thomsonreuters.com/v1/cases';
    static final String apiKey = '(redacted)';
    static final String apiSecret = '(redacted)';

    static String sign(String payload){
        return EncodingUtil.base64Encode(Crypto.generateMac('HmacSHA256', Blob.valueOf(payload), Blob.valueOf(apiSecret)));
    }

    public static void sendCase(){
        Payload p = new Payload();

        String formattedTimestamp = Datetime.now().formatGMT('EEE, dd MMM yyyy HH:mm:ss z');
        String contentType = 'application/json';
        String content = JSON.serialize(p);
        String contentLength = String.valueOf(content.length());

        String dataToSign = String.join(new List<String>{
            '(request-target): post ' + gatewayurl + 'cases',
            'host: ' + gatewayhost,
            'date: ' + formattedTimestamp,
            'content-type: ' + contentType,
            'content-length: '+ contentLength,
            content
        }, '\n');
        System.debug(dataToSign);

        String signature = sign(dataToSign);
        System.debug(signature);

        String authorisation = 'Signature keyId="' + apiKey + '",algorithm="hmac-sha256",headers="(request-target) host date content-type content-length",signature="' + signature + '"';
        System.debug(authorisation);


        HttpRequest req = new HttpRequest();
        req.setEndpoint(endpoint);
        req.setMethod('POST');
        req.setHeader('Authorization',authorisation);
        req.setHeader('Date',formattedTimestamp);
        req.setHeader('Cache-Control', 'no-cache');
        req.setHeader('Content-Type',  contentType);
        req.setHeader('Content-Length', contentLength);
        req.setHeader('Accept', 'application/json');
        req.setBody(content);

        Http http = new Http();
        HTTPResponse res = http.send(req);
        System.debug(res);
        System.debug(res.getBody());
    }

    public class Payload {
        public List<String> secondaryFields;
        public String entityType;
        public List<String> customFields;
        public String groupId;
        public List<String> providerTypes;
        public String name;

        //  Bit stupid constructor, provides sample values. Ideally you'd pass to it a Case record or whatever?
        public Payload(){
            secondaryFields = new List<String>();
            entityType = 'INDIVIDUAL';
            customFields = new List<String>();
            groupId = '418f28a7-b9c9-4ae4-8530-819c61b1ca6c';
            providerTypes = new List<String>{'WATCHLIST'};
            name = 'george w bush';
        }
    }
}

You need to provide your own API keys. Successful request will receive a response with HTTP 201 "Created" and JSON payload starting with something like

{"caseId":"a266ba18-7b63-414b-a0bc-907dd4676970","name":"george w bush" ...