Loading
Upcoming Mandatory Changes to Public Key Infrastructure (PKI)Read More
Salesforce Enforces New Security Requirements in Summer 2026Read More

Marketing Cloud Engagement Extract Installed Package Source IPs via SSJS for Secret Rotation Policy

Publish Date: Jul 3, 2026
Description

Due to the implementation of the client secret rotation policy, Marketing Cloud Engagement administrators need visibility into which external platforms and tools are actively interacting with their accounts.

The REST API Security Events audit endpoint allows administrators to identify exactly where and when an external tool is leveraging the Marketing Cloud API. By capturing these source IP addresses and event timestamps, administrators can pinpoint the specific third-party integrations, middleware, or external applications making inbound calls. This data ensures teams know exactly which platforms need to be updated with the new client secret, preventing unexpected integration downtime during the rotation process.

CRITICAL REQUIREMENT: To use this endpoint, the Audit Trail feature must be enabled within your Marketing Cloud Engagement tenant. If Audit Trail is not already active in your account, an administrator must enable it under Setup before the API will return any security event data.

Resolution

To extract the security event audit payload and automatically populate a target Data Extension, execute the following steps:

Step 1: Create the Target Data Extension

  1. In Marketing Cloud Engagement, navigate to Email Studio > Subscribers > Data Extensions.

  2. Create a standard Data Extension named Audit_SecurityEvents_DE and ensure the External Key matches exactly.

  3. Configure the fields using the table structure below:

Field NameData TypeLengthNullableNotes
createddateDateN/ACheckedStores the timestamp of the event.
midText50CheckedStores the Member ID (Business Unit).
IPaddressText254CheckedLength accommodated for IPv6 strings.
employeenameText254CheckedThe API user name (e.g., "oAuth2.1 app user").
loginstatusText100CheckedStores success or failure statuses.

Note: If you plan to run this script on a recurring daily automation, consider adding a unique field like an auto-incrementing number, or mapping the payload's id field as a Primary Key to prevent duplicate entries from overlapping timeframes.

Step 2: Configure API Credentials

  1. Navigate to Setup > Apps > Installed Packages.

  2. Select or create an existing Server-to-Server API Integration component.

  3. Ensure the component has the Audit Logging > Read permission enabled.

  4. Copy the Client Id, Client Secret, and the unique tenant subdomain string.

Step 3: Create the Automation and Script Activity

  1. Navigate to Journey Builder > Automation Studio.

  2. Create a new Automation.

  3. Drag a Script Activity component onto the canvas workflow.

  4. Copy the complete SSJS code block below and paste it into your Script Activity text layout.

  5. Replace the placeholder values for tenantSubdomain, clientId, and clientSecret with your real package credentials.

  6. Save and run the activity.

<script runat="server">
Platform.Load("Core", "1.1.5");

// Helper function to format dates to ISO 8601 strings for the REST API
function getISO8601String(d) {
    var pad = function(n) { return n < 10 ? '0' + n : n; };
    return d.getUTCFullYear() + '-' +
           pad(d.getUTCMonth() + 1) + '-' +
           pad(d.getUTCDate()) + 'T' +
           pad(d.getUTCHours()) + ':' +
           pad(d.getUTCMinutes()) + ':' +
           pad(d.getUTCSeconds()) + 'Z';
}

try {
    // 1. Configure Credentials
    var tenantSubdomain = "{{et_subdomain}}"; // Replace with your tenant subdomain
    var clientId = "YOUR_CLIENT_ID";         // Replace with Client ID
    var clientSecret = "YOUR_CLIENT_SECRET"; // Replace with Client Secret
    var targetDEKey = "Audit_SecurityEvents_DE";
    
    // 2. Authenticate to get the REST Access Token
    var authEndpoint = "https://" + tenantSubdomain + ".auth.marketingcloudapis.com/v2/token";
    var authPayload = {
        "grant_type": "client_credentials",
        "client_id": clientId,
        "client_secret": clientSecret
    };

    var authReq = new Script.Util.HttpRequest(authEndpoint);
    authReq.emptyContentHandling = 0;
    authReq.retries = 2;
    authReq.continueOnError = true;
    authReq.contentType = "application/json";
    authReq.method = "POST";
    authReq.postData = Stringify(authPayload);

    var authResp = authReq.send();
    var accessToken = "";

    if (authResp.statusCode == 200) {
        var authJSON = Platform.Function.ParseJSON(String(authResp.content));
        accessToken = authJSON.access_token;
    } else {
        throw "Failed to authenticate with Marketing Cloud API. Status: " + authResp.statusCode;
    }

    // 3. Calculate Date Parameters (Past 7 Days)
    var endDate = new Date();
    var startDate = new Date();
    startDate.setDate(startDate.getDate() - 7);

    var endDateStr = encodeURIComponent(getISO8601String(endDate));
    var startDateStr = encodeURIComponent(getISO8601String(startDate));

    // 4. Initialize Data Extension and Loop Control
    var targetDE = DataExtension.Init(targetDEKey);
    var pageSize = 100;
    var page = 1;
    var keepFetching = true;

    while (keepFetching) {
        // Construct endpoint using current page index and date filters
        var auditEndpoint = "https://" + tenantSubdomain + ".rest.marketingcloudapis.com/data/v1/audit/securityEvents" 
            + "?startDate=" + startDateStr 
            + "&endDate=" + endDateStr 
            + "&$pagesize=" + pageSize 
            + "&$page=" + page;

        var req = new Script.Util.HttpRequest(auditEndpoint);
        req.emptyContentHandling = 0;
        req.retries = 2;
        req.continueOnError = true;
        req.contentType = "application/json";
        req.setHeader("Authorization", "Bearer " + accessToken);
        req.method = "GET";

        var resp = req.send();

        if (resp.statusCode == 200) {
            var result = Platform.Function.ParseJSON(String(resp.content));
            var items = result.items || [];

            // If a page returns empty, we have retrieved all records for the timeframe
            if (items.length === 0) {
                keepFetching = false;
                break;
            }

            for (var i = 0; i < items.length; i++) {
                var item = items[i];
                
                // Navigate nested 'employee' object safely
                var empName = "";
                if (item.employee && item.employee.employeeName) {
                    empName = item.employee.employeeName;
                }
                
                // Define the GUID regex pattern (case-insensitive)
                var guidRegex = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i;
                var hasGuid = guidRegex.test(empName);
                
                // Filter IN "app user" but explicitly skip strings that contain a system GUID
                if (empName.toLowerCase().indexOf("app user") !== -1 && !hasGuid) {
                    
                    // Navigate nested 'loginStatus' object safely
                    var statusName = "";
                    if (item.loginStatus && item.loginStatus.name) {
                        statusName = item.loginStatus.name;
                    }

                    // Map fields exactly to your Data Extension schema
                    var row = {
                        "createddate": item.createdDate,
                        "mid": item.memberId,
                        "IPaddress": item.ipAddress,
                        "employeename": empName,
                        "loginstatus": statusName
                    };

                    // Add the record to the Data Extension
                    try {
                        targetDE.Rows.Add(row);
                    } catch (deError) {
                        logError("Failed to insert row for ID " + item.id + ": " + Stringify(deError));
                    }
                }
            }
            
            // Advance to the next page of results
            page++;
            
        } else {
            keepFetching = false;
            throw "Error retrieving data from the Audit API. Status Code: " + resp.statusCode;
        }
    }
} catch (error) {
    logError("Critical Script Exception: " + Stringify(error));
}

function logError(message) {
    Write(message + "\n");
}
</script>

 

Step 4: Analyze Captured Target IPs and Timestamps

Once the Script Activity completes execution, navigate to your target Data Extension. Review the IPaddress and createddate columns to understand exactly when and from where external tools are making calls. Use this logged audit activity to coordinate update times with your integration owners and successfully roll out the rotated client credentials without disrupting live API connections.

Knowledge Article Number

005388368

 
Loading
Salesforce Help | Article