Last Updated on November 1, 2023 by Rakesh Gupta
Big Idea or Enduring Question:
- How do you notify the lead owner when someone removes a lead from the Sales Engagement Cadence?
Objectives:
After reading this blog, you’ll be able to:
- Understand the Change Data Capture
- Understand ActionCadenceTracker object
- Subscribe to Change Events Using an Apex Trigger
- Call an Autolaunched Flow from Apex Class
- And much more.
👉 Previously, I’ve penned an article on Sales Engagement. Why not check them out while you are at it?!
Business Use case
Benjamin Moore, a Salesforce Administrator at Gurukul On Cloud (GoC), has been assigned a specific task. Whenever a telemarketer or the Inside sales team removes a lead (prospect) from the sales engagement cadence, ensure the automatic creation of a task with these details:
- Subject:- Prospect removed from Sales Engagement Cadence
- Status:- Not Started
- Priority:- High
- Due Date:- Today
- Related To:- Lead
- Assigned To:- Lead Owner
What is Sales Engagement Cadence?
Acquiring leads is an important step in any marketing effort. Once Inside Sales Teams have a list of leads, they are ready to undertake outreach. But, wait! Let us take a step back and ponder following questions as a preamble to understanding Sales Cadence: (1) How often do you want your reps to reach out to leads? (2) How would you like them to reach out to lead – using call or email? (2) How do you want to capture call disposition? (3) etcetera.
Sales engagement cadence is a timeline of sales activities and methods that sales reps follow to engage leads. The purpose of a cadence is to make it easy for the Inside Sales Rep to stay on schedule and ensure that prospects aren’t forgotten – i.e., nothing falls through the cracks. For example, if you offer a free consultation on your website and someone fills out a form, the cadence would include a list of things you do to schedule the first meeting.
A Sales Cadence typically includes three different touchpoints: Email, Social Media and, Calls/Voicemails. A cadence, for different sales funnel, may differ but, it should always include a combination of the mentioned three forms of communications.
A Sales engagement Cadence looks something like this:
In other words, Sales Engagement Cadences differ from one type of sales process to the next, but the basic idea remains the same: consistent, sequential touches.
What is Change Data Capture?
Change Data Capture (CDC) is a technique used in databases and data processing systems to identify and capture changes made to the data. The primary purpose of CDC is to ensure that changes in the source data (such as inserts, updates, and deletes) can be easily detected and processed in downstream systems.
Change Data Capture (CDC) in Salesforce is a feature that allows you to track and capture changes made to Salesforce data. It provides a way to capture and deliver changes in data, both in real-time and in a historical manner, making it easier to synchronize data across systems and maintain an up-to-date copy of Salesforce data in external data stores. Here’s a breakdown of what Change Data Capture in Salesforce is all about:
- Real-Time Updates: CDC provides a real-time stream of changes, which means as soon as a change is made in Salesforce, it’s captured and can be acted upon immediately.
- Comprehensive Data Capture: CDC captures not just the changed data but also metadata about the change, such as when the change occurred and what type of change it was (e.g., create, update, delete).
- Standard and Custom Objects: You can enable CDC for both standard Salesforce objects (like Account, Contact) and custom objects.
- Event-Driven Architecture: Changes are delivered as events. Applications can subscribe to these events and take action whenever there’s a change. This allows for a more responsive and dynamic integration between Salesforce and external systems.
- Simplified Integration: CDC can simplify integration challenges. Instead of frequently polling Salesforce for changes, external systems can just listen to the change events and act accordingly.
- High Volume: It’s designed to handle a large volume of change events, ensuring that even enterprises with massive amounts of data can use CDC effectively.
- APIs and Platform Events: Salesforce provides APIs that allow external systems to subscribe to these change events. Additionally, these change events are built on Salesforce Platform Events, making it easier to work with within the Salesforce ecosystem.
- Event Channels: Change events are sent to event channels that have a naming convention like
data/ObjectNameChangeEvent
, whereObjectName
is the name of the Salesforce object, likeAccount
orContact
. - Event Details: Each change event contains information about the change, such as the type of change (create, update, delete, undelete), the record ID, the names of changed fields, and the new values of those fields.
- Salesforce retains change events for 72 hours, allowing systems to catch up if they fall behind in processing events.
Admins can enable Change Data Capture for specific objects in Salesforce through the setup menu. Once enabled, changes to those objects will start generating change events.
Change Data Capture in Salesforce provides a powerful way to keep external systems in sync with Salesforce data without the need for complex integration logic or frequent polling. It’s especially useful for real-time analytics, backup systems, and other applications that require up-to-date Salesforce data.
Potential Solution I – Record-Triggered Flow on Lead
Before we dive into the actual working solution, let me guide you through some potential solutions that may appear to be the easiest and quickest routes at first glance. However, there are specific reasons why these solutions won’t be viable.
If you have experience with managing leads and Sales Engagement Cadences, you may have noticed that Salesforce stores the ActionCadenceId (the ID of the lead’s assigned cadence) on the lead record. Notably, Salesforce sets the ActionCadenceId to null when a prospect is manually removed/complete from the Sales Engagement Cadence.
You might be contemplating the idea of implementing a Record-Triggered After-Save flow (don’t know after-save flow, check this article) on the Lead object and applying the following conditions:
- ActionCadenceId Is Not Null {!$GlobalConstant.False}
- Then query ActionCadenceTracker object to find out the state and CompletionReason = ‘ManuallyRemoved‘
However, you would encounter an obstacle when attempting to save the flow. It would result in an error message stating,
Yes (Decision Outcome) – The $Record.ActionCadenceId field contains a derived value that isn’t supported. In a scheduled or record-triggered flow, $Record doesn’t support some standard fields whose values are derived. Remove the $Record.ActionCadenceId field.
Now you understand why we cannot use this approach to address the aforementioned business use case.
Note: The Cadence and Cadence Assignee fields on lead, contact, and person account records can’t be used to as field criteria in workflow rules, Flows and Process Builder.
Potential Solution II – Record-Triggered Flow on ActionCadenceTracker
This solution might initially seem like a more promising option than the previous one, and you might be wondering why it wasn’t considered earlier. Unfortunately, Salesforce imposes restrictions, preventing the use of Record-triggered Flows and Apex Triggers on this particular SObject type (ActionCadenceTracker).
Sadly, this marks the end of this solution.
Potential Solution III – Schedule-Triggered Flow on ActionCadenceTracker
The third option is to create a schedule-triggered flow for the ActionCadenceTracker object, which is certainly feasible. However, it’s essential to note that this solution is not real-time.
You can configure the schedule-triggered flow to run at intervals, such as every minute or every hour, but it does have its limitations. I strongly recommend considering scalability when opting for this solution.
While this approach may be suitable for certain scenarios, it does not serve as a viable solution for the specific business use case mentioned, which requires real-time notifications.
The question now is, What is the best solution for the business use case above? Let’s get started.
Automation Champion Approach (I-do):
The best solution for the given business use case is to use Change Data Capture (CDC) on the ActionCadenceTrackerChangeEvent object. If you’re not familiar with Change Data Capture (CDC), then I strongly recommend pausing here and completing this Trailhead module.
Before proceeding, you should become familiar with theActionCadenceTracker object in Salesforce. It represents an active cadence target.
An ActionCadenceTracker record is created when you add a target to a cadence. Use ActionCadenceTracker to learn about a running cadence target, including its state, current step, assigned prospect, and reason for completion.
Field Name | Details |
CompletionReason | The reason that the target completed the cadence. This field contains a value if the target’s State is Complete. Possible values are:
|
State | The state of the current action cadence tracker. Possible values are:
|
TargetId | The ID of the target (Contact, Lead) that is assigned to this action cadence. |
Before discussing the solution, let me show you a diagram of the process at a high level. Please spend a few minutes going through the following Flow diagram to understand it.

Guided Practice (We-do):
There are 3 steps to solve Benjamin’s business requirement using Change Data Capture (CDC), an Apex Trigger and Autolaunched Flow. We must:
- Enable the ActionCadenceTracker Object for Change Notifications
- Autolaunched Flow
- Define Flow Properties
- Create a Record Collection Variable to store leads
- Add a Loop element to retrieve the records from the record collection variable
- Add an Assignment element to assign values into a Record Variable (Task)
- Add an Assignment element to add the Record Variable to a Record Collection Variable.
- Add a Create Records element to create Tasks
- Asynchronous Apex Triggers for Change Events
Step 1: Enable the ActionCadenceTracker Object for Change Notifications
- Click Setup.
- In the Quick Find box, type Change Data Capture and select Change Data Capture.
- In Available Entities, select Cadence Tracker (ActionCadenceTracker) and click the > arrow.
- Click Save.
Step 2.1: Define Flow Properties
- Click Setup.
- In the Quick Find box, type Flows.
- Select Flows, then click on the New Flow.
- Select the Autolaunched Flow (No Trigger) option.
- Click on Create.
Step 2.2: Add a Record Collection Variable to Store Leads
- Under Toolbox, select Manager, then click New Resource to store the leads.
- Input the following information:
- ResourceType: Variable
- API Name: varR_Leads
- Data Type: Record
- Object: Lead
- Check Allow multiple values (collection)
- Check Available for Input
- Check Available for Output
- Click Done.
Step 2.3: Add a Loop Element to Retrieve the Records from the Record Collection Variable
- On Flow Designer, click on the +icon and select the Loop element.
- Enter a name in the Label field; the API Name will auto-populate.
- For Collection Variable select {!varR_Leads}.
- For Specify Direction for Iterating Over Collection select the option First item to last item.
- Click Done.
Step 2.4: Adding an Assignment Element to Assign the Values into a Record Variable (Task)
- Create a Record Variable varR_Task type Task to create a task for lead owner.
- On Flow Designer, below the For Each node, click on the +icon and select the Assignment element.
- Enter a name in the Label the API Name will auto-populate.
- Set Variable Values
- Row 1:
- Field: Subject
- Value: Prospect removed from Sales Engagement Cadence
- Click Add Assignment
- Row 2:
- Field: Status
- Value: Not Started
- Click Add Assignment
- Row 3:
- Field: Priority
- Value: High
- Click Add Assignment
- Row 4:
- Field: ActivityDate
- Value: {!$Flow.CurrentDate}
- Click Add Assignment
- Row 5:
- Field: WhoId
- Value: {!Loop_Through_Leads.Id}
- Click Add Assignment
- Row 6:
- Field: OwnerId
- Value: {!Loop_Through_Leads.OwnerId}
- Row 1:
- Click Done.
Step 2.5: Adding an Assignment Element to Add the Record Variable to Record Collection Variable
- Create a Record Collection Variable varR_Tasks type Task to store record variable (created in step 2.4) for the bulk process.
- On Flow Designer, click on the +icon and select the Assignment element.
- Enter a name in the Label the API Name will auto-populate.
- Set Variable Values
- Row 1:
- Field: {!varR_Tasks}
- Operator: Add
- Value: {!varR_Task}
- Row 1:
- Click Done.
Step 2.6: Adding a Create Records Element to Create Tasks
- On Flow Designer, below the After Last node, click on the +icon and select the Create Records element.
- Enter a name in the Label the API Name will auto-populate.
- For How Many Records to Create select Multiple.
- Map Record Collection: {!varR_Tasks}
- Click the X at the top to save your changes.
In the end, Benjamin’s Flow will look like the following screenshot:
Once everything looks good, perform the steps below:
- Click Save.
- Enter Flow Label (CadenceTracker) the API Name will auto-populate.
- API Version for Running the Flow: 59
- Interview Label: CadenceTracker {!$Flow.CurrentDateTime}
- Click Save.
Almost there! Once everything looks good, click the Activate button.
Step 3: Asynchronous Apex Triggers for Change Events
You have the option to receive notifications for changes on the Lightning Platform by subscribing to change events through Apex triggers. While Apex triggers for change events share similarities with those for standard Salesforce objects, there are also notable distinctions. Much like traditional Apex triggers for Salesforce objects, you create a change event trigger for the specific change event associated with the Salesforce object. It’s worth noting that only after-insert triggers are supported in this context.
The change event trigger fires when one or a batch of change events is received. Unlike object triggers, change event triggers run asynchronously after the database transaction is completed. The asynchronous execution makes change event triggers ideal for processing resource-intensive business logic while keeping transaction-based logic in the object trigger. By decoupling the processing of changes, change event triggers can help reduce transaction processing time.
Change event triggers have these characteristics.
- They run under the Automated Process entity. As such, debug logs for the trigger are created by the Automated Process entity, and system fields, such as CreatedById and OwnerId, reference Automated Process.
- They are subject to Apex synchronous governor limits.
- They have a maximum batch size of 2,000 event messages (the number of items in Trigger.New).
- Create an Apex Trigger on ActionCadenceTrackerChangeEvent object.
trigger ActionCadenceTrackerAsyncTrigger on ActionCadenceTrackerChangeEvent (after insert) { ActionCadenceTrackerAsyncTriggerHandler.handleChangeEvent(Trigger.new); }
- Create an Apex class ActionCadenceTrackerAsyncTriggerHandler, as shown below.
public class ActionCadenceTrackerAsyncTriggerHandler { public static void handleChangeEvent(List<ActionCadenceTrackerChangeEvent> changeEvents) { Set<Id> actionCadenceTrackerIds = new Set<Id>(); for(ActionCadenceTrackerChangeEvent event : changeEvents) { EventBus.ChangeEventHeader header = event.ChangeEventHeader; if (header.changetype == 'UPDATE' && event.CompletionReason == 'ManuallyRemoved') { List<Id> recordIds = header.getRecordIds(); actionCadenceTrackerIds.addAll(recordIds); } } if(!actionCadenceTrackerIds.isEmpty()) { findRelatedLeads(actionCadenceTrackerIds); } } private static void findRelatedLeads (Set<Id> actionCadenceTrackerIds) { List<Lead> associatedLeads = [Select Id, OwnerId from Lead where Id IN (SELECT targetId FROM ActionCadenceTracker WHERE Id IN :actionCadenceTrackerIds AND Target.Type = 'Lead')]; if(!associatedLeads .isEmpty()) { runFlow(associatedLeads ); } } private static void runFlow(List<Lead> leads) { Map<String, Object> params = new Map<String, Object>(); params.put('varR_Leads', leads); Flow.Interview.CadenceTracker myFlow = new Flow.Interview.CadenceTracker(params); myFlow.start(); } }
Proof of Concept
From now on, if a user removes a lead from the Sales Engagement Cadence, Change Data Capture (CDC) will publish an event. Later, an Apex trigger and Flow will create a Task for the Lead Owner.
- The first step is to assign Sales Engagement Cadence to a lead.
- The next step is to manually remove the Lead from Sales Engagement Cadence.
- Task created by automation.
Formative Assessment:
I want to hear from you!
What is one thing you learned from this post? How do you envision applying this new knowledge in the real world? Feel free to share in the comments below.
hey Rakesh,
in my flow im trying to create an opportunity related to the target passed from __e to Flow… all other DMLs are fine except for Opp creation… FLOW_ELEMENT_ERROR|Operation not valid for this user type
Any Idea why Im having this error and how to fix it ?
It seems like you’re trying to assign an inactive user or a user with a Chatter Free license to an opportunity.
trigger ActionCadenceTrackerChangeTrigger on ActionCadenceTrackerChangeEvent (after insert) {
Set trackerIds = new Set();
for (ActionCadenceTrackerChangeEvent event : Trigger.new) {
EventBus.ChangeEventHeader header = event.ChangeEventHeader;
trackerIds.add(header.recordIds[0]);
}
Map trackerMap = new Map(
[SELECT Id, ActionCadenceId, TargetId FROM ActionCadenceTracker WHERE Id IN :trackerIds]
);
List eventsToPublish = new List();
Set triggerDispositions = new Set{
‘Opportunity Generated’,
‘Customer Connected’,
‘Future Prospect’,
‘Completed Cadence’,
‘Not Interested’,
‘Not Decision Maker’,
‘No Longer at Company’,
‘Do Not Call’,
‘Email Opt Out’,
‘Do Not Solicit’,
‘Invalid Email’
};
for (ActionCadenceTrackerChangeEvent event : Trigger.new) {
EventBus.ChangeEventHeader header = event.ChangeEventHeader;
ActionCadenceTracker tracker = trackerMap.get(header.recordIds[0]);
if (tracker != null && triggerDispositions.contains(event.CompletionDisposition) && ‘Complete’.equalsIgnoreCase(event.State)) {
CadencetrackerEvent__e newEvent = new CadencetrackerEvent__e(
TargetId__c = tracker.TargetId,
State__c = event.State,
CompletionDisposition__c = event.CompletionDisposition,
CadenceId__c = tracker.ActionCadenceId,
RelatedToId__c = tracker.Id
);
eventsToPublish.add(newEvent);
}
}
if (!eventsToPublish.isEmpty()) {
try {
EventBus.publish(eventsToPublish);
} catch (Exception e) {
System.debug(‘Error publishing events: ‘ + e.getMessage());
}
}
}
I dont know how to add screenshots. sorry!! I managed to have the flow do the fields updates for now, but I noticed that sometimes the target does not get removed from he cadence( i have a remove target from cadence element that removes the passed targetid from all associated cadences)
Also I have attached my trigger that publishes the platform event, and I’m struggling to write a test class for it for deployment.
I really appreciate your time and help Sir.
I am happy to help. Feel free to send me your test class.
It’s an interesting article for those working with Salesforce and sales. It demonstrates how Change Data Capture can automate notification and task creation when leads are removed from a sales cadence. It can be useful for enhancing lead tracking.
Your Welcome, glad you enjoyed it 🙂
Can you please share the screenshot of your flow? I am happy to help.