Platform Events & Change Data Capture
Platform Events implement publish/subscribe messaging on the Salesforce event bus.
25.1 Platform Events
Platform Events implement publish/subscribe messaging on the Salesforce event bus. Define an event like a custom object (suffix __e) with fields, then publish and subscribe from Apex, Flow, LWC (lightning/empApi), CometD/Pub/Sub API clients, or external systems.
Publish After Commit (only if the transaction succeeds) or Publish Immediately (even if it rolls back — good for logging)
High-volume (standard; retained 72 hours) Apex triggers, Flows, Pub/Sub API, CometD, LWC empApi
25.2 Publishing
Publishing counts against DML statement limits; the success result means the event was queued for publishing, not that it was delivered.
List<Order_Shipped__e> events = new List<Order_Shipped__e>();
for (Order o : shippedOrders) {
events.add(new Order_Shipped__e(Order_Id__c = o.Id, Carrier__c = 'UPS', Tracking__c = o.Tracking__c));
}
List<Database.SaveResult> results = EventBus.publish(events);
for (Database.SaveResult sr : results) {
if (!sr.isSuccess()) {
for (Database.Error err : sr.getErrors()) System.debug('Publish error: ' + err.getMessage());
}
}25.3 Subscribing with an Apex trigger
Event triggers:
Support only after insert; run as the Automated Process user by default (configurable with PlatformEventSubscriberConfig metadata — running user and batch size).
Receive batches of up to 2,000 events (configurable lower).
Can throw EventBus.RetryableException to have the batch redelivered (up to 9 retries).
Are asynchronous: the publisher's transaction doesn't wait for subscribers.
trigger OrderShippedTrigger on Order_Shipped__e (after insert) {
List<Task> tasks = new List<Task>();
for (Order_Shipped__e evt : Trigger.new) {
tasks.add(new Task(Subject = 'Order shipped via ' + evt.Carrier__c, WhatId = evt.Order_Id__c));
// checkpoint so a failure later resumes after this event
EventBus.TriggerContext.currentContext().setResumeCheckpoint(evt.ReplayId);
}
insert tasks;
}25.4 Testing platform events
@isTest
static void shipsCreateTasks() {
Order_Shipped__e evt = new Order_Shipped__e(Order_Id__c = someOrderId, Carrier__c = 'DHL');
Test.startTest();
EventBus.publish(evt);
Test.stopTest(); // delivers events published before stopTest
// or: Test.getEventBus().deliver(); // deliver immediately at any point
Assert.areEqual(1, [SELECT COUNT() FROM Task WHERE Subject LIKE 'Order shipped%']);
}25.5 Change Data Capture (CDC)
CDC publishes change events automatically when records are created, updated, deleted, or undeleted. Enable objects under *Setup → Change Data Capture*. Event names: AccountChangeEvent, Invoice__ChangeEvent (custom objects drop the trailing c).
Testing: call Test.enableChangeDataCapture() at the start of the test, perform DML, then Test.getEventBus().deliver().
Platform Events vs CDC: | | Platform Events | Change Data Capture | |-|-----------------|---------------------| | Who defines the payload | You (custom fields) | Salesforce (changed record fields + header) | | Who publishes | Your code, Flow, APIs | The platform, automatically on DML | | Typical use | Business events, decoupling, integrations, logging | Data replication/sync to external systems, async reactions to changes |
trigger AccountChangeTrigger on AccountChangeEvent (after insert) {
for (AccountChangeEvent evt : Trigger.new) {
EventBus.ChangeEventHeader header = evt.ChangeEventHeader;
String changeType = header.getChangeType(); // CREATE, UPDATE, DELETE, UNDELETE, GAP_*, GAP_OVERFLOW
List<String> recordIds = header.getRecordIds();
List<String> changedFields = header.getChangedFields();
if (changeType == 'UPDATE' && changedFields.contains('Rating')) {
System.debug('Rating changed on ' + recordIds + ' to ' + evt.Rating);
}
}
}