Campaigns & Workflows (On‑Device)
Campaigns are your container for describing when to show flows and who will see them. They chain together a series of actions, designed visually so you can express targeting and timing clearly—showing the right flow to the right customer at the right moment. Once published, campaigns run entirely on the customer’s device, making them instant, offline‑resilient, and independent of network timing.
Mental model
- Campaign: the container for who can enter (trigger), how often (frequency), optional goal + exit policy, and a workflow graph of nodes to run.
- Workflow: a visual sequence of actions we call nodes (show a flow, wait, branch, update data, send an event, end) that runs in order and can branch based on conditions.
- Journey: a single customer’s path through a campaign over time. The journey persists locally on a user's device, can pause/resume, and records conversion/exit.
Workflows execute on the device. Nodes like Show Flow present UI immediately; nodes like Time Delay or Time Window pause the journey and schedule resume. Goals and exits are continuously evaluated around node execution.
Who enters (triggers)
Triggers are the actions that start the customer's journey in the workflow. We have two:
- Event trigger: enter when your app records an event with the configured name and an optional property filter.
- Segment trigger: enter when the customer matches a condition based on segments and/or their attributes.
Notes
- Event triggers start a journey immediately when matched.
- Segment membership is evaluated continuously on device and segment triggers start the journey when the customer enters the segment.
Inputs
Field | Type | Required | Description |
---|---|---|---|
type | `"event" | "segment"` | yes |
config.eventName | string | when type = event | Name of the incoming app event that starts the campaign. Examples: app_opened , onboarding_completed . |
config.when | condition | optional (event) | A boolean rule over event properties (built in the visual builder). Used to narrow which events qualify. |
config.match | condition | when type = segment | A boolean rule over segment membership and/or attributes (built in the visual builder). When true, the customer may enter. |
How often (frequency policies)
Re‑entry frequency options:
- once: allow entry if the customer has never completed this campaign. Caution: this mode does not block entry when another journey for this campaign is already live; avoid triggers that can fire repeatedly, or add gating.
- every_rematch: allow entry whenever the trigger matches and there is no live journey for this campaign.
- fixed_interval: allow re‑entry only after the configured interval (seconds).
- If a live journey exists and its age ≥ interval, the system cancels the live journey and starts a new one.
- If no live journey exists, the system checks the time since the last completion.
Inputs
Field | Type | Required | Description |
---|---|---|---|
mode | `"once" | "every_rematch" | "fixed_interval"` |
intervalSeconds | number | when mode = fixed_interval | Minimum seconds between journey starts. If a live journey is older than this, it is cancelled and restarted on re‑trigger. |
Goals and exits
Optional goal snapshot and exit policy are latched at journey start and honored for the life of the journey.
Goal kinds
- event: conversion occurs if a qualifying event’s timestamp lies within [anchor, anchor + window]. Conversion “latches” at the event time even if evaluation happens later (e.g., offline sync).
- segment_enter / segment_leave: conversion is true when membership changes as specified during evaluation, and only if still within the window.
- attribute: a rule over attributes/events/segments that must evaluate to true during evaluation (within the window).
Window and anchor
- window (seconds): per goal or defaulted by campaign type (e.g., paywall, onboarding).
- conversion anchor: when the window starts; defaults to
workflow_entry
.
Exit policy
- on_goal: exit when the goal is met.
- on_stop_matching: exit when a segment‑triggered journey no longer matches its trigger condition.
- on_goal_or_stop: either of the above.
- never: never exit early; run the whole workflow.
Evaluation timing
- Goal and exit are evaluated before starting, before each node, after each node, and on relevant events/segment changes. This provides immediate exits after conversion or stop‑matching.
Inputs (Goal)
Field | Type | Required | Description |
---|---|---|---|
kind | `"event" | "segment_enter" | "segment_leave" |
eventName | string | when kind = event | Name of the event that counts as a conversion. |
segment | string | when kind = segment_enter/segment_leave | The saved segment to observe for enter/leave. |
attributeRule | condition | when kind = attribute | Rule over customer attributes/events/segments that must become true to convert. |
windowSeconds | number | optional | Conversion window length. Defaults by campaign type (e.g., paywall = 21 days, onboarding = 10 days). |
anchor | "workflow_entry" | optional | When the conversion window starts. Defaults to workflow_entry . |
Inputs (Exit policy)
Field | Type | Required | Description |
---|---|---|---|
mode | `"on_goal" | "on_stop_matching" | "on_goal_or_stop" |
Runtime semantics (what actually happens)
High‑level loop:
- Trigger matched → create a journey, snapshot goal/exit/window, and set the entry node.
- Evaluate goal/exit. If an exit should occur, complete the journey.
- Execute the current node and get a result:
- continue([ids]) → advance to the next node.
- async(date?) → pause; schedule resume at date (or indefinite when nil).
- skip(id?) → skip this node; continue at id (or end if nil).
- complete(reason) → complete journey.
- Repeat until async or complete.
Important behaviors
- Show Flow is fire‑and‑forget: it presents a paywall and the journey continues immediately. Add a Wait Until if you need to branch on purchase/dismiss, or a Time Window to hold subsequent nodes.
- Wait Until reacts to events and segment changes. Timeout paths only mature on timer/start resumes (not during reactive resumes); reactive resumes clamp rescheduling to avoid tight loops.
- Time Window checks local device time by default (
useUTC: false
) or UTC (true
). Start == end is treated as “always open.” Overnight windows are supported. - Random Branch assigns a cohort using a 0–100 draw; percentages should sum to 100. Each cohort maps to an outgoing edge in the order you define. If configuration and edges are misaligned, routing falls back to a safe default connection.
- Branch has two outgoing edges: the first edge is taken when the condition is true; the second when false. Multi‑branch has N+1 outgoing edges: the first N align with your conditions in order; the last edge is the default when no condition matches.
Workflow Nodes
Action nodes
Show Flow
Presents a published flow (paywall) in a dedicated window. The journey advances immediately after presentation.
Inputs
Field | Type | Required | Description |
---|---|---|---|
flowId | string | yes | Identifier of the published flow to present. |
Outputs
Edges | Description |
---|---|
1 | Continues to the next connected node immediately after presentation. |
Update Customer
Sets customer properties on device (merged into the profile) and emits telemetry for observability.
Inputs
Field | Type | Required | Description |
---|---|---|---|
attributes | object (key → value) | yes | Properties to set. Values can be string, number, or boolean. |
Outputs
Edges | Description |
---|---|
1 | Continues after attributes are updated. |
Send Event
Emits a synthetic event from the workflow. Useful for analytics breadcrumbs or to trigger other automation.
Inputs
Field | Type | Required | Description |
---|---|---|---|
eventName | string | yes | Name of the event to send. |
properties | object | no | Additional key/value pairs (string/number/boolean) to include on the event. |
Outputs
Edges | Description |
---|---|
1 | Continues after the event is recorded. |
Call Delegate
Calls into your app with a message and optional payload so you can perform custom actions (e.g., refresh entitlements, navigate).
Inputs
Field | Type | Required | Description |
---|---|---|---|
message | string | yes | A short identifier for the action your app should handle. |
payload | object | no | Arbitrary JSON‑serializable data your app can consume. |
Outputs
Edges | Description |
---|---|
1 | Continues after the delegate call is dispatched. |
Timing
Time Delay
Pauses the journey for a fixed duration, then resumes.
Inputs
Field | Type | Required | Description |
---|---|---|---|
durationSeconds | number | yes | How long to pause before continuing. If ≤ 0, the journey continues immediately. |
Outputs
Edges | Description |
---|---|
1 | Continues after the delay elapses (or immediately if ≤ 0). |
Time Window
Continues only when the current time falls inside the configured window; otherwise the journey pauses until the next open.
Inputs
Field | Type | Required | Description |
---|---|---|---|
startTime | string (HH:MM) | yes | Start of the allowed delivery window (time‑of‑day). |
endTime | string (HH:MM) | yes | End of the allowed delivery window (time‑of‑day). If equal to startTime, the window is treated as always open. |
useUTC | boolean | yes | When true, interpret times in UTC; otherwise use the device’s local timezone. |
daysOfWeek | number[] | no | Valid days using iOS numbering: 1=Sun … 7=Sat. Empty/omitted means every day. Overnight windows are supported. |
Outputs
Edges | Description |
---|---|
1 | Continues when the window is open; otherwise waits until the next opening. |
Wait Until
Waits for the first matching path to become true, or for a path‑specific timeout to mature.
Inputs
Field | Type | Required | Description |
---|---|---|---|
paths[].id | string | yes | Label for the path (used in analytics). |
paths[].when | condition | yes | Rule to check. Evaluated reactively on events/segment changes and on timer resumes. |
paths[].timeoutSeconds | number | no | Maximum time to wait for this path before taking it by timeout. Timeouts mature on timer/start resumes (not during reactive resumes). |
Outputs
Edge | Description |
---|---|
one per path | Each defined path maps to an outgoing edge. The first matching path (by rule or by timeout) is taken. |
Logic & experiments
Branch (If/Else)
Routes customers down one of two paths.
Inputs
Field | Type | Required | Description |
---|---|---|---|
condition | condition | yes | Rule to evaluate. |
Outputs
Edge | Description |
---|---|
first | Taken when the condition is true. |
second | Taken when the condition is false. |
Multi‑branch
Tries multiple conditions in order and routes to the first match; falls back to the default path.
Inputs
Field | Type | Required | Description |
---|---|---|---|
conditions[] | condition | yes | Ordered list of rules. The journey takes the first rule that matches. |
Outputs
Edge | Description |
---|---|
first…N | Taken when the corresponding condition (in definition order) is true. |
default | Taken when none of the conditions match. |
Random Split (A/B/n)
Splits traffic into named cohorts by percentage.
Inputs
Field | Type | Required | Description |
---|---|---|---|
branches[].percentage | number (0–100) | yes | Share of customers to route to this branch. All branches should sum to 100. |
branches[].name | string | no | Optional human label for the cohort (e.g., "A", "discount10"). |
Outputs
Edge | Description |
---|---|
one per branch | Edges align with branches in the order defined. Each cohort maps to its corresponding edge. |
Control
Exit
Stops the journey.
Inputs
Field | Type | Required | Description |
---|---|---|---|
reason | string | no | Optional label recorded in analytics. Accepted values include completed , goal_met , expired , error . Defaults to completed . |
Outputs
Edges | Description |
---|---|
0 | No outgoing edges; the journey ends. |
Automatic Events
These events are emitted automatically so you don’t need to duplicate them. System events use a $
prefix. All events include standard metadata like a timestamp and a session id when available.
-
Lifecycle
$app_installed
- When: first launch on a device.
- Properties:
app_version
,install_date
,source
.
$app_updated
- When: app version changes between launches.
- Properties:
previous_version
,app_version
,update_date
,source
.
$app_opened
- When: app enters foreground (also on first launch and after updates).
- Properties:
open_date
,app_version
,source
.
$app_backgrounded
- When: app enters background.
- Properties:
background_date
,source
.
-
Campaign & Journey
$journey_exited
- When: a journey ends (natural completion, goal met, stop‑matching, expired, cancelled, error).
- Properties:
journey_id
,campaign_id
,exit_reason
(completed|goal_met|trigger_unmatched|expired|cancelled|error
),had_conversion
(bool), optionallyduration_seconds
,converted_at
,goal_kind
.
$journey_goal_met
- When: a journey’s goal criteria are met within the conversion window.
- Properties:
journey_id
,campaign_id
,goal_kind
,met_at
,window_seconds
.
-
Flows (paywalls)
$flow_shown
- When: a Show Flow node presents a flow.
- Properties:
journey_id
,campaign_id
,node_id
,flow_id
.
$flow_completed
- When: a presented flow is closed.
- Properties:
flow_id
,journey_id
,campaign_id
,completion_type
(dismissed|purchase|timeout|error
), optionalproducts_shown
; on errors, anerror_message
may be included.
-
Workflow nodes (execution telemetry)
$node_executed
- When: any node finishes execution.
- Properties:
journey_id
,campaign_id
,node_id
,node_type
,result
(continue|async|skip|complete
). Depending onresult
, includesnext_nodes
,resume_at
,skip_to
, orexit_reason
.
$node_branch_taken
- When: a Branch or Multi‑branch chooses a path.
- Properties:
journey_id
,campaign_id
,node_id
,branch_path
(true|false|condition_#|default
),condition_result
(bool).
$node_random_branch_assigned
- When: a Random Split assigns a cohort.
- Properties:
journey_id
,campaign_id
,node_id
,cohort_name
(optional),cohort_value
(0–100 draw).
$node_wait_completed
- When: a Wait Until path wins (by condition or timeout).
- Properties:
journey_id
,campaign_id
,node_id
,matched_path
,wait_duration_seconds
.
$node_errored
- When: a node throws an unrecoverable error.
- Properties:
journey_id
,campaign_id
,node_id
,node_type
,error_message
.
-
Data & integration helpers
$customer_updated
- When: an Update Customer node sets attributes.
- Properties:
journey_id
,campaign_id
,node_id
,attributes_updated
(string array).
$event_sent
- When: a Send Event node emits a custom event.
- Properties:
journey_id
,campaign_id
,node_id
,event_name
,event_properties
.
$delegate_called
- When: a Call Delegate node signals the host app to perform an action.
- Properties:
journey_id
,campaign_id
,node_id
,message
, optionalpayload
.
-
Commerce
purchase_completed
- When: a purchase completes successfully through the built‑in purchase path.
- Properties:
product_id
,price
,display_price
.
purchase_failed
- When: a purchase fails through the built‑in purchase path.
- Properties:
product_id
,error
.
restore_completed
- When: a restore operation completes and finds prior purchases.
- Properties:
restored_count
.
restore_failed
- When: a restore operation fails.
- Properties:
error
.
restore_no_purchases
- When: a restore operation finds no prior purchases.
Notes
- You generally do not need to track these yourself; they are emitted automatically when you use flows, nodes, and the built‑in purchase path.
- For conversion reporting, many teams use
$flow_completed
withcompletion_type = purchase
and/orpurchase_completed
as the success signal.
Conditions (what you can reference)
Conditions are boolean rules you build in the visual editor. You can:
- Check customer attributes (country, plan, install date, etc.).
- Reference event properties and frequencies.
- Test membership in your saved segments.
Worked examples
Example A: “Greet new customers with a paywall, but only during daytime”
- Trigger: segment → matches
is_new_customer
. - Frequency: every_rematch.
- Goal: event
purchase_completed
, window 21 days. - Exit: on_goal_or_stop (stop if they leave
is_new_user
). - Workflow: time_window → show_flow → wait_until (purchase or 24h timeout) → exit.
Example B: “Winback after a dismiss if no purchase in 7 days”
- Trigger: event
viewed_paywall
. - Frequency: fixed_interval 14 days (cancels/refreshes if you trigger again after 14 days).
- Goal: event
purchase_completed
, window 14 days. - Exit: on_goal.
- Workflow: show_flow → wait_until (purchase path or 7‑day timeout) → branch on country → show variant flow → exit.
Practical tips
- Show Flow does not block; add Wait Until if subsequent logic depends on outcomes.
- For “once” campaigns, prevent duplicate live journeys by gating the trigger (e.g., additional segment condition) when multiple events may arrive quickly.
- Time Window uses iOS weekday numbering (1=Sun … 7=Sat); start==end means always open; overnight windows are supported.
- Multi‑branch uses the last path as the default and as the error fallback.
- Random splits should total 100; otherwise the system falls back to a safe default connection.
- Call Delegate lets you integrate app‑specific actions without changing your app integration.
If you want a deeper API reference for individual node fields, see Reference → Workflow Nodes.