Skip to main content

Campaign Actions

An action is what happens when a user taps a button, card, or other interactive element in a Digia Engage campaign. A single element can run one action or an ordered list of them.

Actions belong to the Digia Engage core SDK. Whichever CEP delivers the campaign — CleverTap, MoEngage, WebEngage, or a custom plugin — only decides who sees the experience and when. The core SDK decides what the action does inside your app.

info

Actions behave the same way on every stack — React Native, Android, iOS, and Flutter. Code examples are shown per language for convenience, but the vocabulary, the flow semantics, and the handler contract are identical everywhere.

Campaign authors pick actions in the Digia Engage Dashboard. You never write the wire JSON by hand. The canonical action names on this page are what you'll see when inspecting a campaign payload or an SDK log — they're a reading aid, not something you author.

How an action flow runs

An interactive element carries an ordered list of actions. The SDK runs them one at a time, in order, and moves to the next when the current one completes.

  • Order is preserved. Action 1 runs, then action 2, and so on.
  • A failure is logged, and the flow continues. If the SDK observes an action fail, it logs it and still runs the remaining actions.
  • Variables resolve at tap time. Campaign variables such as {{ product_id }} are interpolated when the user taps, in URL and deep-link targets, Share and Copy text, and Custom KV keys and values.
  • Put Hide last. When something must happen before the surface closes, place Hide at the end of the flow.
warning

A missing variable resolves to its configured fallback or an empty string. Treat every interpolated value as untrusted input: validate identifiers and URLs in your handlers before acting on them.

The handler timing contract

A few actions can be owned by your app (see section 7). Your handler runs as part of the tap's flow, so keep the ordering-sensitive work — navigating, validating, handing off to a service — in the handler itself. If you kick off longer-running background work, run it on your own async primitive (a coroutine, Task, Future, or promise) and own its result and errors. Don't rely on that background work finishing before the next authored action runs; instead, sequence with Hide last.

Action catalog

Every action a campaign can run, and whether your app can own it:

Dashboard actionCanonical typeWhat it doesApp handler
Open URL / Deep linkAction.openUrlRoutes to a screen or opens a web targetOptional
Hide / DismissAction.dismissCloses the current campaign surface
NextAction.nextAdvances a multi-step experience
PreviousAction.previousReturns to the previous step
ShareAction.shareOpens the system share sheet
Copy to clipboardAction.copyToClipBoardCopies text to the clipboard
Request app reviewAction.requestReviewAsks the store for its in-app review prompt
Custom KVAction.customKVSends a key–value payload to your appRequired

The dashboard may emit a surface-specific canonical name for Hide — such as Action.dismissDialog or Action.hideBottomSheet. These all express the same "dismiss the current surface" intent.

Open URL and Deep link are two modes of the same Action.openUrl. The SDK decides which mode to use from the action's launchMode, not from whether the target starts with https:// or a custom scheme.

launchModeModeBehavior
platformDefault or omittedDeep linkLet the system or app route the target
externalApplicationOpen URLOpen in an external app, normally the browser
inAppBrowserOpen URLOpen in an in-app browser

A custom-scheme deep link looks like:

yourapp://products/123?source=engage

Deep link is for in-app navigation. Provide a deepLink handler when your app should own routing; it receives the target string and fully owns the action. Without a handler, the SDK asks the system to open the target.

Open URL is for web destinations. Provide an openURL handler when your app should choose how web links are presented; it receives only the URL, so it also chooses the presentation. Without a handler, the SDK opens the URL with default platform behavior.

See section 7 for how to register these handlers.

Host project setup

Deep links resolve through the native platform, so register your scheme in both host projects regardless of which framework your app is built with:

  • Android — add a matching intent filter to AndroidManifest.xml (custom scheme or App Link).
  • iOS — add a URL type in Info.plist, or configure an associated domain for a universal link.
info

Don't rely on a link action to close the campaign. Add Hide as the last action when the surface should close after navigation.

Hide and Dismiss

Hide closes the current campaign surface — a bottom sheet, dialog, or other dismissible experience. Use it as the final step when the flow must do something else first, such as navigate, share, or run a Custom KV command.

Next and Previous

Next advances a multi-step experience such as a Guide; Previous returns to the preceding step. The experience defines its own boundary behavior — for example, Next on the final step can complete or dismiss the experience.

These actions only make sense on a surface that actually has multiple steps.

Share and Copy to clipboard

Action.share opens the system share sheet with the configured message, after variable interpolation. Action.copyToClipBoard copies the configured message to the clipboard, also after interpolation.

info

The canonical spelling is Action.copyToClipBoard. Older payloads using the copy alias remain readable, but don't author copy_to_clipboard — it isn't a canonical action name.

App-owned actions

Three actions can be handled by your app instead of by default SDK behavior:

  • Deep link — take over in-app navigation.
  • Open URL — choose how web links are presented.
  • Custom KV — run any app behavior that no built-in action expresses. This action always requires a handler.

A registered handler fully owns its action: the SDK does not run its own built-in behavior afterward. Handlers receive only the link target or the Custom KV payload — no campaign context, and no "suppress default" return value. If the SDK observes a handler fail, it logs the failure and continues with the next authored action.

Register handlers

Register handlers once during initialization.

await Digia.initialize({
apiKey: 'YOUR_ACCESS_KEY',
actionHandlers: {
customKV: (payload) => handleCampaignCommand(payload),
deepLink: (url) => appRouter.open(url),
openURL: (url) => void trustedBrowser.open(url),
},
});

You can replace or clear any handler at runtime. Passing a null handler restores the SDK's default link behavior. Register a Custom KV handler before publishing a campaign that uses Custom KV, or those taps have nothing to run.

Digia.setCustomKVHandler(customKVHandlerOrNull);
Digia.setDeepLinkHandler(deepLinkHandlerOrNull);
Digia.setOpenURLHandler(openURLHandlerOrNull);

The same handlers apply across every campaign surface. Register them at app scope so a tap always has a live handler; clear a temporary handler when the screen that owned it goes away.

The deepLink handler receives the link string set on the campaign and navigates inside your app. It runs at app scope — outside your React component tree — so you cannot use the useNavigation() hook; you navigate through a module-level handle. Wire it to whatever navigation library you use (each example below is the function you pass as actionHandlers.deepLink, or later to Digia.setDeepLinkHandler).

info

This is in-app navigation — moving around an app that is already running. It is not the OS-level universal link / App Links setup (iOS Associated Domains, Android App Links). You only need those if the same links must also open your app from outside, such as from a push notification, an email, or the browser.

1. Expo Router — the router singleton takes a path string directly, which makes it the simplest to wire:

import { router } from 'expo-router';

Digia.setDeepLinkHandler((url) => router.push(url)); // url authored as "/product/123"

2. React Navigation (imperative) — navigate through a navigationRef, because the handler lives outside the component tree:

// navigation.ts
import { createNavigationContainerRef } from '@react-navigation/native';
export const navigationRef = createNavigationContainerRef();
// then: <NavigationContainer ref={navigationRef}> … </NavigationContainer>
import { navigationRef } from './navigation';

Digia.setDeepLinkHandler((url) => {
if (!navigationRef.isReady()) return; // ignore taps that arrive before the navigator mounts
// Map the campaign's url to a screen + params however your app prefers:
navigationRef.navigate('Product', { id: url.split('/').pop() });
});

3. React Navigation (reuse your linking config) — if your app already resolves URLs declaratively, convert the string with getStateFromPath instead of hand-mapping each route:

import { getStateFromPath } from '@react-navigation/native';
import { navigationRef } from './navigation';

Digia.setDeepLinkHandler((url) => {
const state = getStateFromPath(url, linking.config); // your existing linking.config, url like "/product/123"
if (state && navigationRef.isReady()) navigationRef.resetRoot(state);
});

4. React Native Linking (OS / external links) — when the target is a real URL scheme or an https:// universal link, hand it to Linking:

import { Linking } from 'react-native';

Digia.setDeepLinkHandler((url) => void Linking.openURL(url)); // "myapp://product/123" or "https://…"

This last one is also exactly what Digia does when you register no deepLink handler at all — it runs Linking.openURL, guarded by canOpenURL, falling back to the action's fallback_url if one is set. So for navigation inside your app, register approach 1 or 2; reach for Linking only when the target is a genuine OS URL.

warning

Agree the link format with your campaign authors. Whatever they enter in the deep-link action — /product/123, myapp://product/123, or https://… — your handler must expect that exact shape; it is case- and format-sensitive. Pick one convention and use it across your campaigns. An optional fallback_url on the action covers the case where the primary link cannot be opened.

Custom KV

Action.customKV sends a string-to-string payload to your app. It's the supported extension point for anything the built-in actions don't express. Non-string values in the payload are discarded during parsing, so design for a flat string map.

Reach for Custom KV to:

  • open an app feature that has no stable deep link;
  • apply a coupon or select a product in app state;
  • start an app-owned workflow such as sign-in, checkout, or support;
  • call an app service after validating the payload;
  • record a distinct business outcome that campaign lifecycle analytics don't already capture.

For ordinary navigation, prefer Deep link; for web destinations, prefer Open URL. Reserve Custom KV for behavior those two can't express.

Use a versioned command schema

Configure a small, stable payload in the dashboard:

{
"action": "open_product",
"version": "1",
"product_id": "{{ product_id }}",
"source": "engage_campaign"
}

action names an allowlisted command; version lets your app evolve the contract without guessing which payload shape it received. Keep both stable.

Handle Custom KV in the app

Dispatch on action through an explicit allowlist, validate every field, and ignore unknown commands and versions.

import { Digia } from '@digia-engage/core';

function handleCampaignCommand(payload: Record<string, string>): void {
if (payload.version !== '1') return;

switch (payload.action) {
case 'open_product': {
const productId = payload.product_id?.trim();
if (!productId) return;
navigationRef.navigate('ProductDetails', { productId });
return;
}
case 'apply_coupon': {
const coupon = payload.coupon?.trim();
if (!coupon) return;
void cartService.applyCoupon(coupon).catch(reportActionError);
return;
}
default:
console.warn('Unknown Engage action', payload.action);
}
}

await Digia.initialize({
apiKey: 'YOUR_ACCESS_KEY',
actionHandlers: {
customKV: handleCampaignCommand,
},
});

Custom KV best practices

  • Allowlist commands. Dispatch through an explicit table or switch. Never use payload data to select arbitrary code, classes, methods, routes, or URLs.
  • Version the contract. Keep action and version stable, and reject unknown versions safely.
  • Validate every field. Treat campaign data and interpolated variables as external input.
  • Keep payloads small and string-only. Pass identifiers and intent, not whole domain objects.
  • Never include secrets or sensitive personal data. Payloads can surface in dashboards, logs, and diagnostic reports.
  • Make destructive commands safe to repeat. Debounce repeated taps and use idempotency for charges, submissions, or mutations.
  • Own recovery. A registered handler suppresses SDK fallback, so log failures and present any user-facing recovery yourself.
  • Put Hide last. Validate or start your work before the surface dismisses.
  • Avoid duplicate analytics. Digia Engage already records campaign lifecycle events. Emit an app event only for a distinct business outcome.

Request app review

Action.requestReview asks the platform to present its in-app review prompt.

warning

The platform controls eligibility and quota, and returns no rating or completion result. The SDK cannot guarantee that a prompt appeared. Treat it as best-effort — never make business-critical behavior depend on the prompt being shown.

Test before publishing

Test an action-heavy campaign on every target surface your app uses.

  • Verify each authored action produces the expected result.
  • Test deep links from a cold start and while the app is already active.
  • Confirm missing or malformed variables fail safely.
  • Verify Open URL and Deep link behavior both with and without your handlers.
  • Confirm Custom KV ignores unknown commands and versions.
  • Ensure repeated taps can't repeat a destructive operation.
  • Verify Hide runs at the intended point in the flow.
  • Treat Request app review as best-effort, not a guaranteed prompt.