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.
To extract the security event audit payload and automatically populate a target Data Extension, execute the following steps:
In Marketing Cloud Engagement, navigate to Email Studio > Subscribers > Data Extensions.
Create a standard Data Extension named Audit_SecurityEvents_DE and ensure the External Key matches exactly.
Configure the fields using the table structure below:
| Field Name | Data Type | Length | Nullable | Notes |
| createddate | Date | N/A | Checked | Stores the timestamp of the event. |
| mid | Text | 50 | Checked | Stores the Member ID (Business Unit). |
| IPaddress | Text | 254 | Checked | Length accommodated for IPv6 strings. |
| employeename | Text | 254 | Checked | The API user name (e.g., "oAuth2.1 app user"). |
| loginstatus | Text | 100 | Checked | Stores 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
idfield as a Primary Key to prevent duplicate entries from overlapping timeframes.
Navigate to Setup > Apps > Installed Packages.
Select or create an existing Server-to-Server API Integration component.
Ensure the component has the Audit Logging > Read permission enabled.
Copy the Client Id, Client Secret, and the unique tenant subdomain string.
Navigate to Journey Builder > Automation Studio.
Create a new Automation.
Drag a Script Activity component onto the canvas workflow.
Copy the complete SSJS code block below and paste it into your Script Activity text layout.
Replace the placeholder values for tenantSubdomain, clientId, and clientSecret with your real package credentials.
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>
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.
005388368

We use three kinds of cookies on our websites: required, functional, and advertising. You can choose whether functional and advertising cookies apply. Click on the different cookie categories to find out more about each category and to change the default settings.
Privacy Statement
Required cookies are necessary for basic website functionality. Some examples include: session cookies needed to transmit the website, authentication cookies, and security cookies.
Functional cookies enhance functions, performance, and services on the website. Some examples include: cookies used to analyze site traffic, cookies used for market research, and cookies used to display advertising that is not directed to a particular individual.
Advertising cookies track activity across websites in order to understand a viewer’s interests, and direct them specific marketing. Some examples include: cookies used for remarketing, or interest-based advertising.