Monitor Platform Event Limits – Platform Event Tracker

Author: Bruce Tollefson Published: May 8, 2022; Modified: May 9, 2022

Trying to identify the current org limit for platform events can be cumbersome. Tracking platform event usage limits is even more difficult. The usage limit is a rolling 24 hour limit based on the number of purchased platform events your org has. If it is the out of the box limit then it is 50k external subscribed events (more can be purchased). This can be used quickly and if hit then the connection will be disconnected and will receive: 403::Organization total events daily limit exceeded. With this error you won’t be able to connect until after the rolling 24 hour limit is below the exceeded limit. This will not affect events being fired as that is a different limit.

There are supposedly 2 ways in order to identify the number of events that have been delivered (sent to external subscribers). One way is through the REST API by getting the limits:


In order to create some events quickly that are published and delivered for an external subscriber you can use the Event Listener and publish a few generic events.

METHOD: GET /services/data/v47.0/limits

Then searching for the MonthlyPlatformEvents:

"MonthlyPlatformEvents": { "Max": 300000, "Remaining": 290000 },

Notice this will be a total for the month which means you would need to take the: Max – Remaining – Previous Total for all Daily Amounts. This means there is a large room for error. Also the API changed after v47.0 and the MonthlyPlatformEvents is changed to MonthlyPlatformEventsUsageEntitlement which at the time of this post is broken and shows:

"MonthlyPlatformEventsUsageEntitlement": { "Max": 0, "Remaining": 0 },

This makes that method less than ideal. The other option is to query the PlatformEventUsageMetric obj:


This object contains usage data for event publishing and CometD-client delivery. But this may not work quite like how you may be imagining as well. When queried from Dev Console it only shows one row but you can see has more stored:

SELECT Id, Name, StartDate, EndDate, Value FROM PlatformEventUsageMetric
Dev Console Query

Next try running the following in Dev Console to look at all of the rows:

for(PlatformEventUsageMetric peum :[SELECT Id, Name, StartDate, EndDate, Value FROM PlatformEventUsageMetric]){ system.debug(peum); }

By taking a look at the StartDate and EndDate you can see all but the last two / four rows (depends on if Change Data Capture events were published and subscribed) are at the 00 time stamp indicating the start date is at the beginning of the previous day as the end date for a 24 hour period as a history. However the last two rows will indicate the rolling 24 hours. The important thing to note is the name which can have 1 of 4 values:

  • CHANGE_EVENTS_DELIVERED—Number of change data capture events delivered to CometD clients
  • CHANGE_EVENTS_PUBLISHED—Number of change data capture events published
  • PLATFORM_EVENTS_DELIVERED—Number of platform events delivered to CometD clients
  • PLATFORM_EVENTS_PUBLISHED—Number of platform events published

We will be covering tracking PLATFORM_EVENTS_DELIVERED as that is generally the lowest limit and most sought after to track but the others can be tracked quickly with a couple of small changes. For this we will be creating a custom object with the API Name of Platform_Event_Tracker__c with the following fields:

  • Api Name
  • Data Type
  • EndDate__c
  • DateTime
  • StartDate__c
  • DateTime
  • UniqueName__c
  • Text (Unique)
  • Value__c
  • Number (18,0)

Next create a schedulable class. This class will query the PlatformEventUsageMetric object for records that are created today create a Platform_Event_Tracker__c record and add that to a map. The map will remove the previous day records and override with current rolling 24 hour. The insert the records. There is a uniquename which will remove a duplicate insert based on if a platform event or change data capture event was delivered or published the previous day but not today. Schedulable_PlatformEventUsageCheck:

public class Schedulable_PlatformEventUsageCheck implements Schedulable { public void execute(SchedulableContext context) { //map for name uniqueness Map<String, Platform_Event_Tracker__c> peumMap = new Map<String, Platform_Event_Tracker__c>(); //loop through Order By EndDate ASC with the most recent making sure only the rolling 24 hour record is being tracked and overwritten for(PlatformEventUsageMetric peum :[SELECT Id, Name, StartDate, EndDate, Value FROM PlatformEventUsageMetric where EndDate = Today Order By EndDate ASC]){ Platform_Event_Tracker__c pet = new Platform_Event_Tracker__c(); String name = peum.Name +' '; pet.Name = name; pet.StartDate__c = peum.StartDate; pet.EndDate__c = peum.EndDate; pet.Value__c = peum.Value; pet.UniqueName__c = name;//when saved this will throw any errors for duplicates, a duplicate could occur if the previous day had events being published / delivered but not today as the end date would be todays date. peumMap.put(peum.Name, pet); } List<Database.SaveResult> srList = Database.insert(peumMap.values()); // Iterate through each returned result change to save logs if you would like for (Database.SaveResult sr : srList) { if (sr.isSuccess()) { // Operation was successful, so get the ID of the record that was processed System.debug('Successfully inserted account. Account ID: ' + sr.getId()); } else { // Operation failed, so get all errors for(Database.Error err : sr.getErrors()) { System.debug('The following error has occurred.'); System.debug(err.getStatusCode() + ': ' + err.getMessage()); System.debug('Account fields that affected this error: ' + err.getFields()); } } } } }

Next you can schedule the class hourly with the DynamicJobScheduler and running the following in Anonymous Apex:

DynamicJobScheduler.scheduleRecurringJob('Platform Event Tracker', 60, 'Schedulable_PlatformEventUsageCheck');

Next you can create a subscribed report and only send the report if the values get to x number.

Github repo.

Leave a Reply

Your email address will not be published. Required fields are marked *