Deep Dive: Designing Scalable Salesforce CDC Integrations with Filtered Channels and Enriched Fields
Change Data Capture is easy to demonstrate and harder to operate well.
The demo is: update a Salesforce record and receive an event. The production problem is: receive the right changes, with enough context to process them, at sustained volume, while surviving duplicates, disconnects, downstream failures, schema evolution, and replay.
Salesforce custom channels, event filtering, and CDC field enrichment are valuable because they improve the event boundary before the subscriber starts doing work.
1. Start with the Naive Architecture
Salesforce CDC -> broad subscription -> integration -> client filter -> Salesforce lookup -> downstream
This architecture can work. It is also common to discover three scaling problems:
- most delivered events are irrelevant to this consumer;
- the event lacks an unchanged external ID or routing value;
- every relevant event triggers another Salesforce query.
The subscriber becomes a filtering and hydration layer instead of a focused event processor.
2. Introduce a Domain-Oriented Custom Channel
Salesforce custom channels are defined with PlatformEventChannel and populated with PlatformEventChannelMember metadata.
For CDC, the channel type is data. Members select change-event entities such as AccountChangeEvent.
A channel can group multiple compatible CDC entities. For example:
CustomerUpdates__chn
|- AccountChangeEvent
`- ContactChangeEvent
This is more than convenience. A coherent channel can become a domain integration contract.
Do not create one giant channel simply because grouping is possible. Align membership to a real consumer/domain boundary.
3. Move Coarse Filtering to Salesforce
A channel member can define filterExpression. Salesforce describes these expressions as SOQL-based with a subset of supported operators and field types.
Suppose a downstream master-data system only processes a specific customer segment. Instead of receiving every Account change:
All Account changes -> consumer -> discard 85%
use:
All Account changes -> Salesforce channel filter -> relevant subset -> consumer
What improves?
The subscriber receives less noise, performs less deserialization and local filtering, and the stream itself communicates business scope.
What does not improve automatically?
The underlying Salesforce business changes still happen and CDC still exists as an event mechanism. Filtering should be understood as selecting delivery on the custom channel, not as erasing the source event from existence.
And filtering does not solve duplicates, idempotency, replay, or downstream capacity.
4. Add Enrichment for Stable Context
CDC is about changes. Integration processing often needs values that did not change.
The classic example is an immutable external ID. If Phone changes, the external ID may still be essential for the downstream update.
Salesforce supports enrichedFields on CDC channel members and documents a maximum of 10 fields.
AccountChangeEvent
filter: business-relevant subset
enrichedFields:
- External_Id__c
- Business_Unit__c
Now the event can carry the correlation/routing context without forcing a query after every event.
Don't duplicate filter fields
Salesforce states that fields used by the filter expression are automatically included in delivered change events. Preserve enrichment slots for other necessary context.
5. Decide What Belongs in the Event
A useful test is whether a field makes the event independently processable.
Strong enrichment candidates:
- external IDs;
- compact routing keys;
- tenant/business-unit identifiers;
- parent/correlation identifiers.
Weak candidates:
- fields included "just in case";
- large descriptive fields the consumer rarely uses;
- attempts to turn CDC into a complete snapshot.
If the consumer truly needs broad current state, make that lookup explicit rather than abusing enrichment.
6. Consume Correctly with Pub/Sub API
Pub/Sub API provides a gRPC/HTTP2 interface and uses Apache Avro for event payloads.
Salesforce documents a CDC semantic difference that deserves attention: Pub/Sub CDC messages contain record fields including unchanged fields, and unchanged fields can deserialize with empty values. Do not decide what changed by inspecting whether a field appears in the object.
Use the change-event header:
changedFieldsfor changed fields;nulledFieldsfor explicit null transitions;diffFieldsfor diff-encoded values.
These are bitmap fields in the raw Avro representation and must be decoded correctly.
This matters even more when enrichment is enabled: a field can be intentionally present as context without being a changed field.
7. Design Flow Control and Backpressure
A smaller filtered stream can still spike.
Pub/Sub API uses pull subscription semantics. Control how many events you request, bound concurrent processing, and make downstream capacity visible.
A robust flow looks like:
Pub/Sub fetch
|
v
bounded processing queue
|
+--> transform/validate
|
v
idempotent downstream write
|
v
safe checkpoint
Do not acknowledge operational success merely because the event was deserialized.
8. Replay Is a Recovery Tool, Not a Recovery Strategy
Salesforce documents 72-hour retention for platform and CDC events on the event bus. Replay IDs identify positions in the retained stream.
That gives consumers a valuable recovery window, but architecture must answer:
- where is the last safely processed replay position stored?
- when is it advanced?
- what happens after an outage longer than retention?
- how is a reconciliation/backfill performed?
- how are duplicates handled during replay?
Salesforce documents Managed Event Subscriptions as Beta, allowing committed replay progress to be stored server-side. Evaluate it with its Beta status clearly understood.
9. Idempotency Is Non-Negotiable
Distributed event delivery and retries mean a subscriber should be able to see the same logical work more than once without corrupting the downstream state.
Use an idempotency strategy appropriate to the destination: upsert by external ID, event/version tracking, conditional updates, or a processed-event ledger where justified.
Do not build a design whose correctness depends on "Salesforce will only send this once."
10. Treat Channel Metadata as an API Contract
A filter change can remove events a subscriber used to receive. An enrichment change can remove context the subscriber requires.
Therefore:
- version-control channel/member metadata;
- review changes with consumer owners;
- deploy through environments;
- test positive and negative filter cases;
- test schema and null behavior;
- document the channel's business meaning.
11. Observability for Production
Measure more than uptime.
Track event receipt rate, successful processing rate, lag, retry count, duplicate count, reconnects, downstream latency, checkpoint age, and reconciliation discrepancies.
A filtered channel should also have a volume expectation. A sudden drop to zero can be a broken filter just as easily as a quiet business day.
12. When Not to Use This Pattern
Do not add filtered channels and enrichment reflexively.
A plain CDC subscription may be simpler when almost every event matters and the consumer already has the required context. Client-side filtering can be better when each consumer has rapidly changing local rules. A query/API pattern can be better when the consumer requires a large current snapshot rather than change semantics.
Architecture improves when each feature solves a real problem.
Final Architecture
Salesforce transaction
|
v
Change Data Capture
|
v
Custom CDC Channel
- domain membership
- filterExpression
- enrichedFields
|
v
Salesforce Event Bus
- retained events / replay IDs
|
v
Pub/Sub API
- gRPC / HTTP2
- Avro
- pull / flow control
|
v
Integration Subscriber
- decode change header
- validate
- idempotency
- bounded processing
- retry/recovery
|
v
Downstream Systems
|
v
Checkpoint + Observability + Reconciliation
Filtered channels make the stream smaller and more meaningful. Enriched fields make events more self-contained. Pub/Sub API provides an efficient consumption interface. Production reliability still comes from the architecture around them.
References
- Salesforce Developers: Stream Events at Scale with Event Filters and Field Enrichment
- Salesforce Developers: Explore the Event Platform API with the Extended Postman Collection
- Salesforce Pub/Sub API: Get Started
- Salesforce Pub/Sub API: Event Deserialization Considerations
- Salesforce Pub/Sub API and the Expanded Event Bus
- Salesforce Pub/Sub API: Managed Event Subscriptions (Beta)