Apex Integration
An Apex Final Action passes audience results to custom Apex logic. For new implementations, use FinalAction_ManagedAsync_Interface. Campaign Audience Builder then owns asynchronous batching and calls your logic once for each scope of audience records.
Choose an interface
Section titled “Choose an interface”| Interface | Use it when | Async owner |
|---|---|---|
lb.FinalAction_ManagedAsync_Interface |
You are creating a new Final Action. This is the recommended, heap-safe integration. | Campaign Audience Builder |
lb.FinalAction_Interface |
You explicitly need maximum control over async orchestration and are prepared to own batching, source loading, retries, and notifications. This is the legacy interface. | Your Apex class |
The legacy interface remains supported for backward compatibility. Do not select it only because an older example uses it.
Recommended: managed asynchronous execution
Section titled “Recommended: managed asynchronous execution”Implement lb.FinalAction_ManagedAsync_Interface and its single instance method:
global interface FinalAction_ManagedAsync_Interface { void runSync(String actionApiName, Map<String, Object> args);}Despite the method name, runSync is also the entry point for managed asynchronous work. For a small audience, CAB can invoke it once in the initiating transaction. For a larger audience, CAB starts package-managed work and invokes it once per scope.
Your class must be global, have an accessible no-argument constructor, and implement runSync as an instance method. Do not add runAsync or implement Database.Batchable for the managed interface.
Example
Section titled “Example”This example updates a field selected by the person running the Final Action. Setting a field to a chosen value is naturally safe to repeat if Salesforce retries a scope.
global with sharing class DynamicFieldUpdate_FinalActionImpl implements lb.FinalAction_ManagedAsync_Interface {
global DynamicFieldUpdate_FinalActionImpl() {}
global void runSync(String actionApiName, Map<String, Object> args) { List<SObject> results = (List<SObject>) args.get( lb.FinalAction.Variable.AUDIENCE_RESULTS.name() );
if (results == null || results.isEmpty()) { return; }
String fieldName = (String) args.get('fieldName'); Schema.SObjectField field = results[0] .getSObjectType() .getDescribe() .fields .getMap() .get(fieldName);
if (field == null || !field.getDescribe().isUpdateable()) { throw new IllegalArgumentException( 'The selected field is not available for update.' ); }
Object value = castValue(field.getDescribe().getSOAPType(), args.get('value')); for (SObject result : results) { result.put(fieldName, value); } update results; }
private static Object castValue(Schema.SoapType dataType, Object value) { switch on dataType { when Boolean { return Boolean.valueOf(value); } when Integer { return Integer.valueOf(value); } when Double { return Double.valueOf(value); } when else { return value; } } }
}Managed execution behavior
Section titled “Managed execution behavior”- CAB loads only the audience records and fields needed for the current scope. It can read from the current audience snapshot, Platform Cache, or persisted audience-member records as appropriate.
AUDIENCE_RESULTScontains the complete result list for a synchronous invocation and the current scope for a managed asynchronous invocation.- CAB constructs a fresh class instance and argument map for every asynchronous scope. Do not carry state between
runSynccalls. - Make
runSyncsafe for ordinary batch-retry behavior. Callouts are supported, but a callout that partly succeeds before throwing can be repeated when a scope is retried. - CAB manages applicable completion notifications. The class should not send a second completion notification unless that is part of its business behavior.
- Snapshot-backed subset actions use the managed interface. The legacy interface has no entry contract for those subset snapshots.
Arguments
Section titled “Arguments”args contains predefined CAB values and the custom input variables configured for the Final Action.
| Key | Value |
|---|---|
AUDIENCE_VERSION_ID |
The ID of the audience version being processed. |
AUDIENCE_RESULTS |
The audience records for this invocation. During managed async execution, this is the current scope rather than the entire audience. |
BATCH_SIZE |
The Batch Size configured for the Final Action. |
GET_SUBSET |
Whether the request targets the audience’s subset scope. |
NOTIFY_WHEN_DONE |
Whether the caller requested a completion notification. CAB handles the notification for managed async execution. |
These keys are available through lb.FinalAction.Variable. If a custom input is configured to receive AUDIENCE_RESULTS, CAB also refreshes that input with the current scope for every managed asynchronous invocation.
Configure the Final Action
Section titled “Configure the Final Action”- Open CAB Settings, go to Configure → Automation, and find Final Actions.
- Select New.
- Enter the Final Action Name and Final Action API Name.
- Set Type to Apex.
- In Apex Class, select a class that implements
lb.FinalAction_ManagedAsync_Interfaceor the legacylb.FinalAction_Interface. - Optionally enter a Description, Success Message, and Custom Permission Name under the available properties and advanced settings.
- Set Batch Size. It is the maximum result count CAB attempts synchronously and the maximum records passed to each managed asynchronous
runSyncscope. The default is 500. - Choose the audience object supported by your logic. The action is available only for audiences with that result object.
- Add every audience field your Apex logic reads.
Idis always included. - Define any custom input variables that people should complete when they run the action. The input name becomes its key in
args. - Save and activate the Final Action.
For the example above, create fieldName and value input variables. The class uses their API names as keys in the argument map.
Legacy: maximum-control async orchestration
Section titled “Legacy: maximum-control async orchestration”Use lb.FinalAction_Interface only when you need maximum control that the managed execution lifecycle does not provide. Typical reasons include a custom Queueable chain, external job orchestration, specialized checkpointing, or custom completion behavior.
global interface FinalAction_Interface { void runSync(String actionApiName, Map<String, Object> args); void runAsync(String actionApiName, Map<String, Object> args);}With this interface:
- CAB calls your
runAsyncmethod when the result count exceeds the configured Batch Size. - Your class owns the complete async lifecycle, including record loading, batching or chaining, retries, limit handling, and notifications.
- The async call does not provide a materialized audience in
AUDIENCE_RESULTS; your orchestration must load the correct audience scope. lb.FinalAction.getFinalActionQueryLocator(...)is a compatibility helper, not the recommended large-audience strategy. In Platform Cache mode it materializes the full audience into heap before returning a query locator and is unsafe for large audiences.- Snapshot-backed subset dispatch is not supported by the legacy interface.
Do not implement both interfaces as a way to combine their behavior. If a class implements both, CAB gives the managed interface precedence for asynchronous dispatch.
Migrate a legacy class
Section titled “Migrate a legacy class”To adopt managed async execution:
- Replace
lb.FinalAction_Interfacewithlb.FinalAction_ManagedAsync_Interface. - Remove
runAsync,Database.Batchable, the query-locator setup, and subscriber-owned notification code. - Keep the business operation in the instance
runSyncmethod and process only the records inAUDIENCE_RESULTS. - Confirm that the operation is safe to retry and does not depend on state from an earlier scope.
- Review the configured Batch Size with representative data.
Run the Final Action
Section titled “Run the Final Action”After activation, the Final Action is available from the audience results action menu when the audience object and user permissions match its configuration. Review the description and input values, then select Run. CAB runs small actions immediately and moves larger or subset-backed actions to background processing.

