RemoteKeyCalloutEvent

Notifies subscribers of callouts that fetch encrypted key material from a customer endpoint. This object is available in API versions 45.0 and later.

The RemoteKeyCalloutEvent captures events related to the success or failure of a callout that fetches encrypted key material from an end point. Based on the Platform Events framework, a RemoteKeyCalloutEvent is published every time a callout is made to an external key service. This event lets you monitor your cache-only key callouts in real time, and receive alerts about any errors that might occur. You can subscribe to events with after insert Apex triggers and store events in custom objects, security information event management (SIEM), or other back-end systems.

Supported Calls 

describeSObjects()

Supported Subscribers 

SubscriberSupported?
Apex TriggersYes
Flows 
Processes 
Pub/Sub API 
Streaming API (CometD)Yes

Subscription Channel 

/event/RemoteKeyCalloutEvent

Special Access Rules 

Access to RemoteKeyCalloutEvent data requires purchasing Salesforce Shield or Shield Platform Encryption. The RemoteKeyCalloutEvent only applies to callouts that fetch cache-only key material.

Event Delivery Allocation Enforced 

Yes

See Also

Fields 

Details 

Type: textarea

Properties: Nillable

Description: A JSON representation with more information about the StatusCode. Not all status codes (for example, SUCCESS) show a populated Details field. Populated Details fields include key-value pairs that you can use to make Apex triggers and other programmatic assertions.

EventUuid 

Type: string

Properties: Nillable

Description: A universally unique identifier (UUID) that identifies a platform event message. This field is available in API version 52.0 and later.

ReplayID 

Type: string

Properties: Nillable

Description: Represents an ID value that is populated by the system and refers to the position of the event in the event stream. Replay ID values aren’t guaranteed to be contiguous for consecutive events. A subscriber can store a replay ID value and use it on resubscription to retrieve missed events that are within the retention window.

RequestIdentifier 

Type: string

Properties: Nillable

Description: When Replay Detection for Cache-Only Keys is enabled, a unique marker automatically generated and sent with every callout. This marker includes the key identifier, a nonce generated for that callout instance, and the nonce required from the endpoint.

StatusCode 

Type: picklist

Properties: Nillable, Restricted picklist

Description: A code that characterizes the error. The full list of status codes is available in the WSDL file for your org.

TenantSecretID 

Type: reference

Properties: Nillable

Description: The record ID of the tenant secret associated with the published event.

Usage 

To view a RemoteKeyCalloutEvent and perform custom actions after your callout, create an after insert Apex trigger in Dev Console. These triggers let you assign custom actions for your event. You can set in-app alerts and send email alerts to people who maintain your key service, including users who don’t have a Salesforce login.

For longer-term monitoring, you can store RemoteKeyCalloutEvent data in custom objects and custom fields, SIEM, or other back-end systems. Then use business rules to send alerts. For example, you can set an alert that sends admins an email when something is wrong with a key service.

Here’s an example of an after insert trigger that stores RemoteKeyCalloutEvent results in a custom object called Key Service Callout Log. The custom object also draws data from the TenantSecret object.

Field LabelField NameData Type
Key Service Callout Log IDNameAuto Number
DetailsDetails__cText(255)
Replay DetectionReplay_Detection__cText (255)
Status CodeStatus_Code__cText(255)
Tenant Secret IdTenant_Secret_Id__cText(50)
Tenant Secret StatusTenant_Secret_Status__cText(255)
TypeType__cText(100)
VersionVersion__cNumber(10,0)

If you use this trigger sample, adjust the field API names to suit your needs.

1trigger RemoteKeyCalloutEvent on RemoteKeyCalloutEvent (after insert){ 
2    List<Key_Service_Callout_Log__c> l = new List<Key_Service_Callout_Log__c>();
3    Set<ID> TenantSecretIds = new Set<ID>();
4    Map<ID, TenantSecret> TenantSecrets;
5    for(RemoteKeyCalloutEvent event : Trigger.new){
6        if(event.TenantSecretId != null && !TenantSecretIds.contains(event.TenantSecretId))
7            TenantSecretIds.add(event.TenantSecretId);
8    }
9    if(TenantSecretIds != null && !TenantSecretIds.isEmpty())
10      TenantSecrets = new Map<ID, TenantSecret>([SELECT Type, Version, Status FROM TenantSecret where Id In: TenantSecretIds]);
11    
12    for(RemoteKeyCalloutEvent event : Trigger.new){
13        Key_Service_Callout_Log__c log = new Key_Service_Callout_Log__c();
14      log.Status_Code__c = event.StatusCode;
15        log.Tenant_Secret_ID__c = event.TenantSecretId;
16          log.Replay_Detection__c = event.RequestIdentifier;
17      log.Details__c = event.Details;
18        if(TenantSecrets != null && TenantSecrets.containsKey(event.TenantSecretId)){
19            log.Type__c = TenantSecrets.get(event.TenantSecretId).Type;
20            log.Version__c = TenantSecrets.get(event.TenantSecretId).Version;
21            log.Tenant_Secret_Status__c = TenantSecrets.get(event.TenantSecretId).Status;
22        }
23        l.add(log);
24    }
25    
26    insert l;
27}

Then, you can use this test case to verify that the trigger is working

1@IsTest
2public class without sharing TestRemoteKey { //important: do not enforce sharing
3  @IsTest
4  public static void myUnitMethod1(){
5    List<RemoteKeyCalloutEvent> eList = new List<RemoteKeyCalloutEvent>();
6    List<TenantSecret> tsList = [Select Id, Type, Status From TenantSecret];
7    for(TenantSecret ts : tsList){
8      RemoteKeyCalloutEvent e = new RemoteKeyCalloutEvent();
9      e.TenantSecretId = ts.Id;
10      e.RequestIdentifier = '22222'+ts.Id;
11      e.StatusCode = 'SUCCESS';
12      eList.add(e);
13    }
14    Test.startTest();
15    try {
16       EventBus.publish(eList);
17      Test.getEventBus().deliver();
18      System.debug('delivered... ');
19    } catch(Exception ex) {
20      System.debug(ex.getMessage());
21      Boolean expectedExceptionThrown = ex.getMessage().contains('New Event Cannot be Created') ? true : false;
22      System.AssertEquals(expectedExceptionThrown, true);        
23    }

To troubleshoot callout errors, review the StatusCode and Details fields. These fields give you information about remote key callout errors or exceptions in raw JSON format. Successful, empty callout, and timeout responses return empty Details fields.