Skip to content

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.

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.

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.

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;
}
}
}
}
  • 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_RESULTS contains 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 runSync calls.
  • Make runSync safe 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.

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.

  1. Open CAB Settings, go to ConfigureAutomation, and find Final Actions.
  2. Select New.
  3. Enter the Final Action Name and Final Action API Name.
  4. Set Type to Apex.
  5. In Apex Class, select a class that implements lb.FinalAction_ManagedAsync_Interface or the legacy lb.FinalAction_Interface.
  6. Optionally enter a Description, Success Message, and Custom Permission Name under the available properties and advanced settings.
  7. Set Batch Size. It is the maximum result count CAB attempts synchronously and the maximum records passed to each managed asynchronous runSync scope. The default is 500.
  8. Choose the audience object supported by your logic. The action is available only for audiences with that result object.
  9. Add every audience field your Apex logic reads. Id is always included.
  10. Define any custom input variables that people should complete when they run the action. The input name becomes its key in args.
  11. 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 runAsync method 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.

To adopt managed async execution:

  1. Replace lb.FinalAction_Interface with lb.FinalAction_ManagedAsync_Interface.
  2. Remove runAsync, Database.Batchable, the query-locator setup, and subscriber-owned notification code.
  3. Keep the business operation in the instance runSync method and process only the records in AUDIENCE_RESULTS.
  4. Confirm that the operation is safe to retry and does not depend on state from an earlier scope.
  5. Review the configured Batch Size with representative data.

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.