Not verified hands-on. This article was written from primary documentation rather than reproduced in an org, so it carries no tested-release badge. If you run it and something differs, say so in the comments and it gets tested and corrected.

In one sentence: Change Data Capture publishes a near-real-time event whenever a record is created, updated, deleted or undeleted, and the event carries only the fields that changed plus a header describing the change. It is the right choice when you want to replicate Salesforce data somewhere else. It is the wrong choice when you want to describe a business fact, because the subscriber ends up coupled to your field names.

Turning it on

Setup has a Change Data Capture page where you select objects. Every org gets a small free allocation of objects. Beyond that it is a paid add-on. Once selected, an object publishes to a standard channel such as /data/AccountChangeEvent or, for custom objects, /data/Invoice__ChangeEvent.

Subscribe through the Pub/Sub API over gRPC, through the older CometD endpoint, or inside Salesforce with an Apex trigger on the change event object. That last option is genuinely useful for decoupling logic from the record trigger, though it runs asynchronously and in a separate transaction, so do not use it for anything the user needs to see immediately.

What a change event contains

Two parts. The header, and the changed fields.

The header, ChangeEventHeader, is where the information actually lives:

  • entityName: the object, for a subscriber listening on a channel carrying several.
  • recordIds: an array, because one transaction can change many records identically and Salesforce will collapse them into a single event.
  • changeType: CREATE, UPDATE, DELETE, UNDELETE, or one of the gap variants.
  • changedFields: which fields the update touched. In the Pub/Sub API this arrives bitmap-encoded and you decode it against the schema.
  • nulledFields: fields set to null, which you need because a null in the payload is otherwise indistinguishable from a field that was not sent.
  • commitTimestamp, commitNumber, transactionKey: enough to reassemble the original transaction and to order events correctly.

For an update, the body carries the new values of the changed fields only. Not the old values. If you need before-and-after you have to keep the previous state yourself, which is a real design cost and the reason some teams keep a shadow copy on the subscriber side.

Gap events

Sometimes Salesforce cannot construct a complete event. Field-level security changes, certain internal conditions, or a transaction that changed more records than the platform will pack into one message. When that happens you get a gap event: the header, with a changeType of GAP_CREATE, GAP_UPDATE, GAP_DELETE or GAP_OVERFLOW, and no field payload.

Retention and replay

Events are retained for three days. Each carries a replay ID, and a subscriber reconnects by asking for everything after the last one it processed. Ask for -1 and you get only new events. Ask for -2 and you get everything still in the window.

Three days is the entire safety net. A subscriber that is down over a long weekend comes back to a replay ID the platform no longer recognises, and the recovery is a full reconciliation query, not a replay. So store the last processed replay ID durably, alert when the subscriber stops committing it, and write the reconciliation job before you need it.

CDC or Platform Events

Use CDC when the subscriber wants your data. Replication to a warehouse, a search index, a cache, a downstream system that mirrors records. You get it for free without writing publish logic, and every field change is covered including ones made by a data load.

Use Platform Events when the subscriber wants to know that something happened. An order was approved, a case breached its SLA, a payment failed. You define the event’s fields, you decide when it fires, and you can change the underlying object model without breaking anyone. CDC gives you none of that: rename a field and every subscriber has to be told.

Frequently asked

Does CDC fire for every change? It fires for record-level changes committed to the database, including those from the API and from automation. It does not cover every object, and not every field is included for every object.

Do subscribers see fields they are not allowed to see? No. Field-level security is applied for the subscribing user, which is one of the situations that produces gap events.

Is there a delivery limit? Yes. Event deliveries are metered per day and shared across subscribers, so three clients on one channel cost three times as much as one. Consolidate consumers where you can.