[SalesForce] System.UnexpectedException: Script-thrown exception on HTTP callout

I'm trying to create an Apex callout and running into the following error message:

System.UnexpectedException: Script-thrown exception

My analysis shows that

  • System.UnexpectedException is not catchable in Apex.
  • There are some reports of exceptions with “Script-thrown exception” message by other users, but the exception type in those cases happen to be System.CalloutException. [1]
  • The only reported case of a System.UnexpectedException with the same message appears to be triggered by log level of Callouts. (Note: the recommended workaround did not solve the problem in our case.) [2]

This is the code that causes the error:

HttpRequest req = new HttpRequest();
req.setEndpoint('callout:AmazonConnectAPI/contact/start-recording');
req.setMethod('POST');
String body = String.format(
  '\'{\'"ContactId": "{0}","InitialContactId": "{1}","InstanceId": "{2}","VoiceRecordingConfiguration": \'{\'"VoiceRecordingTrack": "{3}"\'}\'\'}\'',
  new List<String>{
    contactId,
    initialContactId,
    instanceId,
    voiceRecordingTrack
  }
);
if (body != '') {
  req.setBody(body);
}
Http http = new Http();
HttpResponse resp = http.send(req);

Other notable facts:

  • The HTTP callout is made to a Named Credential called AmazonConnectAPI
  • AmazonConnectAPI is configured for "https://connect.us-east-1.amazonaws.com“
  • The authentication method of the Named Credential is Named Principal with protocol "AWS Signature Version 4"
  • The authentication method uses the credentials for an IAM user with a full-access managed policy to Amazon Connect.
  • The formatting of the body is correct. There is no syntax error.
  • The error comes from this line HttpResponse resp = http.send(req);

Best Answer

I was having some issues with my Named Credential (Named Principal) AWS Signature Version 4 and found some resolutions for the below error:

HttpResponse resp = http.send(req);
System.UnexpectedException: Script-thrown exception

1. Named Credential Name

Double check your 'callout:Named_Credential' in Apex matches the Named Credential API 'Name'.

No brainer, but it got me for a few hours.

2. URL Encoded Endpoint

Make sure to escape any URL reserved characters: https://developers.google.com/maps/documentation/urls/url-encoding.

String endpoint = 'callout:Named_Credential/contactId/start=2021-02-10Thh:mm:ss.msm-00:00';

Should probably be:

String endpoint = 'callout:Named_Credential/contactId/start%3D2021-02-10Thh%3Amm%3Ass.msm-00%3A00';

No brainer, but it got me for a few more hours.

3. Escaping Curly Braces in String.format()

I just ran this in Dev Console for your body and got the below:

System.debug((Map<String,Object>) JSON.deserializeUntyped(String.format(
  '\'{\'"ContactId": "{0}","InitialContactId": "{1}","InstanceId": "{2}","VoiceRecordingConfiguration": \'{\'"VoiceRecordingTrack": "{3}"\'}\'\'}\'',
  new List<String>{
    'contactId',
    'initialContactId',
    'instanceId',
    'voiceRecordingTrack'
  }
)));

15:46:47:008 FATAL_ERROR System.JSONException: Unexpected character (''' (code 39)): was expecting comma to separate OBJECT entries at [line:1, column:171]

Changing the last few escaped curly braces from "{3}"\'}\'\'}\'', to "{3}"\'}\'}', got me a correct string

System.debug((Map<String,Object>) JSON.deserializeUntyped(String.format(
  '\'{\'"ContactId": "{0}","InitialContactId": "{1}","InstanceId": "{2}","VoiceRecordingConfiguration": \'{\'"VoiceRecordingTrack": "{3}"\'}\'}',
  new List<String>{
    'contactId',
    'initialContactId',
    'instanceId',
    'voiceRecordingTrack'
  }
)));

15:40:31:006 USER_DEBUG [45]|DEBUG|{ContactId=contactId, InitialContactId=initialContactId, InstanceId=instanceId, VoiceRecordingConfiguration={VoiceRecordingTrack=voiceRecordingTrack}}

Hope it helps.