- Platform Events: what they are, when they fire, and their at-least-once delivery semantics
- Change Data Capture (CDC): what it captures and how it differs from Platform Events
- Streaming API (PushTopic): the legacy mechanism and why it is being deprecated
- The Pub/Sub API: the modern gRPC-based alternative for high-throughput streaming
- Event replay and durable subscriptions: how to handle subscriber downtime without missing events
- The architectural decision tree: which mechanism to use for which integration scenario
The Event-Driven Integration Landscape
Salesforce's event-driven capabilities evolved over a decade from simple outbound messaging to a comprehensive event bus. The evolution matters because different mechanisms were built for different purposes and carry different architectural guarantees. Understanding the lineage prevents the common mistake of choosing a mechanism based on familiarity rather than fit.
The three current mechanisms are: Platform Events (custom event objects published by Apex, Flows, or the API, consumed by Apex triggers, Flows, or external subscribers), Change Data Capture (system-generated events that fire on every create/update/delete/undelete of tracked Salesforce objects, consumed by Apex triggers or external subscribers), and the Pub/Sub API (a modern gRPC-based streaming interface that consolidates Platform Events and CDC delivery for external consumers). The legacy PushTopic/Streaming API is deprecated and should not be used for new implementations.
Platform Events in Depth
A Platform Event is a custom metadata type — you define it in Setup with fields just like a custom object. Published events are stored on Salesforce's event bus and delivered to subscribers within seconds. The event bus retains events for 72 hours, enabling event replay for subscribers that were offline or missed events.
Publishing Platform Events is straightforward from Apex or Flows. The event is treated like a standard SObject insert — EventBus.publish() for synchronous publishing or inserting the Platform Event SObject through the standard DML path. External systems publish events via the REST API using the standard /sobjects/{EventName__e} endpoint. This bidirectional publishing model makes Platform Events the correct mechanism for cross-system event signalling in both directions.
// Publishing a Platform Event from Apex
Order_Shipped__e shipEvent = new Order_Shipped__e(
Order_Id__c = order.Id,
Tracking_Number__c = trackingNum,
Carrier__c = 'FedEx',
Ship_Date__c = Date.today()
);
Database.SaveResult result = EventBus.publish(shipEvent);
if (!result.isSuccess()) {
// Handle publish failure
for (Database.Error err : result.getErrors()) {
System.debug('Platform Event publish error: ' + err.getMessage());
}
}
// Subscribing from Apex trigger (fired asynchronously)
trigger OrderShippedHandler on Order_Shipped__e (after insert) {
for (Order_Shipped__e event : Trigger.new) {
// Process each shipped order event
System.debug('Order shipped: ' + event.Order_Id__c);
}
}
Platform Event delivery to external subscribers uses the CometD long-polling protocol (legacy) or the Pub/Sub API gRPC protocol (recommended). CometD clients connect to the /cometd/{version} endpoint and subscribe to /event/{EventName__e}. The client must maintain a persistent connection and handle reconnection logic. The Pub/Sub API provides a cleaner gRPC-based subscription that is more reliable at scale.
Change Data Capture Architecture
Change Data Capture (CDC) publishes a change event for every create, update, delete, and undelete operation on CDC-enabled objects. Unlike Platform Events (which contain only the fields explicitly set in the event), CDC change events contain the header (change type, entity name, record ID, transaction ID, commit timestamp) and the changed field values — specifically the values that changed, not the entire record state. This delta-only payload is important for high-volume objects where transmitting the full record on every update would be prohibitive.
CDC must be enabled per object in Setup under Change Data Capture. Standard objects and custom objects can be enabled. The change event is published to a channel named /data/{ObjectName}ChangeEvent for specific objects (e.g., /data/AccountChangeEvent) or /data/ChangeEvents for all enabled objects. Subscribers choosing the wildcard channel receive all CDC events for all enabled objects on a single subscription.
CDC change events include a changedFields header that lists which fields were modified. Combined with the event payload, this allows the subscriber to build the full changed state without re-querying Salesforce — reducing the integration to pure event-driven, zero-poll architecture. For objects with high update frequency, this is significantly more efficient than polling-based change detection.
// CDC event payload structure (JSON representation)
{
"schema": "AccountChangeEvent",
"payload": {
"ChangeEventHeader": {
"entityName": "Account",
"recordIds": ["001XXXXXXXXXXXX"],
"changeType": "UPDATE",
"changedFields": ["AnnualRevenue", "Industry", "LastModifiedDate"],
"changeOrigin": "com/salesforce/api/rest/60.0",
"transactionKey": "000e4000-0a22-9a4f",
"sequenceNumber": 1,
"commitTimestamp": 1716134400000,
"commitUser": "005XXXXXXXXXXXX"
},
"AnnualRevenue": 5000000.0,
"Industry": "Technology"
// Only changed fields included — not the full record
}
}
The Pub/Sub API: Modern Streaming
The Salesforce Pub/Sub API is a gRPC-based API introduced to replace the CometD-based Streaming API for external consumers. It supports subscribing to Platform Events, Change Data Capture events, and Monitoring events through a single gRPC interface. The Pub/Sub API uses Apache Avro for message serialisation, which is more compact and type-safe than the JSON used by the CometD streaming interface.
The Pub/Sub API supports event replay via a ReplayPreset in the subscribe request — LATEST (events from now forward), EARLIEST (earliest retained events, up to 72 hours back), or a custom replay ID for resuming from a specific position. The replay mechanism provides durable subscriptions — if a consumer goes offline for up to 72 hours, it can catch up on missed events without any missed events being unrecoverable.
For high-throughput scenarios — subscribing to CDC on high-volume objects like Case or Order on large orgs — the Pub/Sub API outperforms CometD significantly. CometD has throughput limits of approximately 200 events per second per channel; the Pub/Sub API scales to thousands of events per second. New integrations should always use the Pub/Sub API rather than CometD.
Event Replay and Durable Subscriptions
The 72-hour event retention window is one of the most important architectural guarantees in Salesforce event-driven integration. It means that a subscriber that goes offline for up to 72 hours can resume from its last processed event ID and receive all events it missed, in order, without gaps. This is dramatically better than a polling-based architecture where a subscriber that misses a polling window permanently misses any changes that were overwritten before the next poll.
Replay ID tracking is the subscriber's responsibility. The subscriber must persist the ReplayId of the last successfully processed event. On restart, it provides this ReplayId in the subscribe request to resume from where it left off. A subscriber that does not persist the ReplayId (or stores it only in memory) will lose its position on restart and must choose between EARLIEST (reprocessing up to 72 hours of events) or LATEST (missing all events during downtime).
Choosing the Right Mechanism
The decision tree for selecting an event mechanism starts with directionality. If an external system needs to notify Salesforce of a business event, use Platform Events published via REST API. If Salesforce needs to notify an external system of a data change without the external system polling, use CDC (for object-level changes) or Platform Events published from Apex/Flow (for business-logic-defined events). If the need is internal Salesforce event-driven processing (decouple a trigger from its downstream actions), use Platform Events consumed by Apex trigger or Flow.
For the connection mechanism from external systems, always use the Pub/Sub API for new implementations. Use CometD only for legacy systems where migrating to gRPC is not feasible. Never implement a polling loop against the Salesforce REST API for change detection if the object supports CDC — the event-driven alternative is always more efficient, more reliable, and consumes fewer API calls.
Key Takeaways
- Platform Events are developer-defined with arbitrary payloads — use them for explicit business events (Order Placed, Payment Received) in both directions between Salesforce and external systems.
- Change Data Capture is system-generated and publishes field-level changes for tracked objects — use it when an external system needs to react to any data modification on a Salesforce object without polling.
- The Pub/Sub API (gRPC-based) is the modern external subscription mechanism — it replaces CometD/Streaming API and should be used for all new external integrations consuming Platform Events or CDC.
- Event retention is 72 hours — subscribers must persist their ReplayId to resume after downtime. Gaps longer than 72 hours require a reconciliation query to recover missed changes.
- CDC change events contain only changed fields (not the full record), identified via the changedFields header — this delta payload is what makes CDC efficient for high-update-volume objects.
- Never use PushTopic/Streaming API for new implementations — it is deprecated. Platform Events via Pub/Sub API replaces all legacy streaming use cases.
Test Your Understanding
1. An external ERP system needs to receive a notification whenever a Salesforce Opportunity is moved to "Closed Won" stage. The ERP does not need to know about other Opportunity field changes. Which mechanism is most appropriate?
2. A CDC subscriber was offline for 80 hours due to a system outage. It resumes with EARLIEST replay preset. What data does it receive?
3. A CDC change event for an Account update shows changedFields: ["AnnualRevenue"]. The subscriber needs the current value of the Name field to process the event. What should the subscriber do?
Discussion & Feedback