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);
        }
    }
}