A practical guide to creating campaigns โ event triggers, target groups, step-by-step examples, and ready-to-build scenarios.
Optimove receives player information in two totally different ways. They do not mix.
Every night, Optimove pulls a fresh copy of every player's profile โ balance, level, game history, transaction history. Think of it as a daily "state of every player."
When a player does something right now โ places a bet, deposits, levels up โ a signal fires instantly to Optimove. These are moments, not profiles.
When a player places a bet and an event fires, their daily batch profile is not updated. The batch profile only changes in the next daily sync. Events and batch data live in completely separate systems. A scheduled campaign sees yesterday's snapshot, not what just happened 5 minutes ago.
Every night, Optimove pulls a fresh copy of the entire player database. Here's how that works.
Player profiles, balances, levels, game activity, and transaction history โ all stored in our database.
Optimove automatically pulls this data once per day into its own system.
Each player gets a profile inside Optimove with all their attributes up-to-date.
Filter players by any attribute: "level 5+, deposited $50+ in past 30 days."
Campaign fires at a set time (e.g. 5 AM) to everyone in the target group.
Batch data is a snapshot. It shows where every player stood at the end of yesterday. It is not live.
Batch data is not a single file. It's four separate views, each with a different purpose. Optimove reads all four nightly and cross-references them by Player ID.
Everything below is already live. Sourced from the four batch views โ raw fields passed through directly, or computed by Optimove from the raw data.
When a player does something, a signal fires instantly. These drive campaigns for players who are active right now.
Player places bet, deposits, levels up...
Signal sent instantly with rich data attached
Brief buffer (milliseconds) to ensure delivery
Event arrives in Optimove within seconds
If conditions match, campaign fires immediately
Each event carries a snapshot of that player's current state โ so real-time campaigns have fresh data without needing to query the database.
Fires whether player won or lost. Carries current balances, level, risk score, game result, and DWH
aggregate metrics (bet/win amounts, purchase history, bonus claimed) โ all fresh at the moment of the bet.
Note: completed_redemption_*
fields excluded to stay within Optimove's attribute count limit.
Carries updated balances, level, risk score, purchase amount, sw_package_type, is_first_time_deposit,
affiliate/marketing IDs, and DWH aggregate metrics. Note: completed_redemption_*
fields excluded to stay within Optimove's attribute count limit.
Carries new level, rank, and current balances. Great trigger for congratulation campaigns.
Carries redemption amount and current pending redemption total.
Carries updated redemption history windows. Triggers for completed redeemers.
Carries cumulative bonus totals. Enables campaigns for frequent bonus claimers.
Triggers onboarding campaigns. Carries initial profile state.
Fires periodically while a player has an open session. Carries a full snapshot of all DWH aggregate metrics
โ could be used to keep Optimove's RT Attributes V2 profile fresh even during sessions with no bets or
deposits in the Phase 3. Also
includes completed_redemption_*
fields (unlike bet_finished and deposit).
An event firing does not update a player's batch profile. The batch profile updates once per night via the daily sync โ period. A scheduled campaign scheduled for tomorrow will use tomorrow night's batch data, not the events that fired today.
bet_finished event can fire with bet_amount_today = 0 even though the bet amount
is non-zero โ the DWH has not yet reflected it.
redemption_requested,
redemption_completed, level_change, bonus_claim,
user_active, and initial_daily_balance.
0 (never null).
All string fields (affiliate_id, sw_package_id, etc.) default to "N/A" when empty.
Before creating a campaign, events must be configured in Optimove's back office as one of three types. Each event is one type only โ not all three. Swivel currently has Simple and Repeated events configured. Uncompleted Sequence events need to be set up separately in Optimove BO before they can be used in campaigns.
Fires once each time the defined action happens. The campaign evaluates trigger conditions against each event independently and fires if all pass. This is the most common type.
The event fires every time the action happens โ always. The trigger activates when the count reaches N. Optimove tracks occurrences internally โ no counter value needs to be sent from Swivel. Two counting modes:
Time window: to require N occurrences within a timeframe (e.g. 3 deposits within 3h), add the same event multiple times in the trigger sequence and set the timeframe at the bottom of the trigger config โ do not use a single "Occurred N times" condition for this.
Optimove watches for a first action (A), then waits for a follow-up action (B). If B does not happen within the time window, the campaign fires. This is your cart-abandonment equivalent โ the player started a journey but didn't finish it.
The type of campaign depends on which channel it listens to โ batch data or events.
Every player's profile is up-to-date as of yesterday's snapshot
e.g. "Not active in 7+ days AND country = US AND is_blocked = No AND total purchases over $100"
e.g. 5 AM ET โ when servers are quiet and the message feels like a morning nudge
"We miss you โ here's 500 GC to come back!"
โ Best for: win-back, reactivation, loyalty milestones, risk-based segmentation
Places a bet, deposits, levels up, completes a redemption...
Signal arrives at Optimove within seconds, carrying fresh player data
e.g. "Deposit event AND first purchase ever AND balance now over 1000 SC"
Pop-up, push notification, or bonus fires while the player is still in session
โ Best for: purchase upsell, level-up celebration, first-deposit welcome, redemption follow-up
A target group is a saved filter of player attributes. Both scheduled and triggered campaigns require a target group โ it defines the eligible pool. Use common sense about what belongs here: balance changes every session so a batch snapshot will always be stale. Average purchase amount means nothing for a player with two purchases. Stick to attributes that are stable and statistically meaningful โ country, account status, player type, lifecycle stage.
e.g. "Last login was more than 7 days ago"
e.g. "Lifetime purchases over $50"
e.g. "Lifecycle stage = Active, level 5 or higher"
These are two completely different places where Optimove checks if a player should receive a campaign. They must carry different types of data.
Use stable, slowly-changing attributes. Common sense applies: balance changes every session โ a batch snapshot is always going to be behind. Averages and ratios are only meaningful if the player has enough data points to make them statistically valid.
The trigger condition is evaluated the instant an event arrives โ against the data attached to that event. The event payload carries live values from the exact moment of the action.
Existing/New (visitor) user ยท not banned ยท opted in ยท country = US
BET_FINISHED
{ sc_balance: 0.8,
risk_score: 45 }
risk_score < 70 โ
sc_balance < 1 โ
Top-up offer shown to player
Every campaign โ triggered or scheduled โ follows the same setup flow. Here's what each step means and how to get it right.
The Target Group is the pool of players who could receive this campaign. For triggered campaigns, use slow-changing attributes only. Never put balance, purchase amounts, or redemption data here โ those values are stale by the time Optimove reads them.
Pick an event from Swivel. The event type (Simple / Repeated / Uncompleted Sequence) is already determined by how the event was configured in Optimove BO โ you don't choose it here. You can also configure a trigger as a combination of events within a time window (e.g. sign_up followed by deposit within 30 min, or 3 bet_finished events within 1 hour).
Conditions are checked the instant the event fires, against the live data attached to that event. This is where real-time data lives. All conditions must be true for the campaign to fire.
bet_finished fires instantly but
bet_amount_today in that payload
may still be 0. Use threshold
conditions (> X) not exact
equality (= N) when building on
DWH aggregate fields.
Choose your channel and craft the message. For triggered campaigns the timing is automatic โ the campaign fires the moment conditions are met. No scheduling needed.
Not all attributes are equal. Some give reliable segments from day one; others need sufficient player history before they mean anything. Getting this wrong means campaigns firing to the wrong people.
These four filters keep you compliant, exclude the wrong audience, and ensure campaigns reach reachable players only. Add any extra filters on top.
Some attributes only make sense after a player has enough history. Using them too early produces garbage data and campaigns that fire to the wrong people.
One large outlier purchase completely skews the average for players with few transactions. A player who bought once at $50 has an "average" of $50 โ but that's a sample of one, not a behavioral signal.
Number of Purchases, Lifetime โฅ 5 before using any average purchase
filter
A player with one redemption has either a 0% or 100% ratio โ mathematically correct but statistically useless. You can't segment meaningfully on ratios with 1โ2 data points.
Number of Redeems, Lifetime โฅ 3 before using any redemption rate
filter
Optimove's churn model needs months of behavioral history to be accurate. Players who registered recently produce unreliable scores that may cause you to target (or exclude) the wrong people.
Months Since Registration โฅ 3 before relying on Churn Probability
Score
Win ratios fluctuate wildly with small sample sizes. A player who won 5 of their first 7 bets looks like a 71% winner โ but that's pure variance, not a reliable signal.
Number of Bets Made โฅ 20 for reliable segmentation
This measures activity over the last 90 days. For a player registered 20 days ago, "last 3 months" is mostly empty โ the frequency will be misleadingly low and segment them incorrectly.
Days Since Registration โฅ 90 when using any "Last 3 Months"
frequency filters
Raw purchase amounts like "lifetime total > $50" mean different things depending on how long a player has been registered. A 2-week player at $50 and a 2-year player at $50 are very different segments.
Purchase Percentile By LCS over raw
Total Purchase Amount for spend-based segmentation
Both values change over time. Putting them in a Target Group means Optimove evaluates them at an unknown time against stale batch data. By the time an event fires, the TG membership may not reflect the player's current state. Instead, include risk_score < 70 or redemption_rate < 75% in the trigger condition โ that reads the live value from the event payload at the exact moment it fires.
Every event carries a rich context object with the player's current state. Real production payloads for every event Swivel sends to Optimove.
purchase_count_* fields may not yet
reflect this deposit โ the DWH can lag ~30s behind. See the
user_active recommendation below.
bet_amount_* and
win_amount_* aggregates may lag ~30s. High-frequency event โ fires on
every single bet.
level_name, level_rank, and
level_experience_threshold. No DWH lag concerns for level itself.
bonus_type is either "cash" or
"freeSpins". For free spins: claimed_amount is
null, free_spin_count has the value.
sc_balance is low after the redemption.
All DWH-sourced aggregate fields below are available in every event as campaign conditions. They are sourced from ClickHouse and carry a ~30-second lag behind the action that fires the event.
user_active
for DWH-Aggregate CampaignsCampaigns triggered by action events (deposit, bet_finished) and filtered on DWH aggregates are fundamentally unreliable. Here is why โ and the right approach.
purchase_count_lifetime,
bet_amount_30d,
completed_redemption_count_lifetime
etc. has a ~30-second propagation delay. When a
deposit
event fires, the DWH aggregate for that same deposit has almost certainly not updated
yet. The event arrives with stale counts. You cannot know which way it will go โ
sometimes the count is already updated, sometimes it is not. The result is a
non-deterministic campaign that fires unpredictably.
deposit
purchase_count_lifetime = 2โฅ 3
fails โ campaign does not fire
user_active
purchase_count_lifetime = 3
purchase_count_lifetime = 3
โ settled โ
โฅ 3
passes โ campaign fires reliably
user_active
user_active fires approximately every 2 minutes while the player is
active in a session. It is controlled by a server heartbeat with Redis dedup โ
so it fires at most once per interval per user. By the time it fires, the DWH
has had ample time to process any preceding action.
Use any DWH aggregate as the condition โ for example
purchase_count_lifetime >= 3,
bet_amount_30d >= 1000, or
completed_redemption_count_lifetime >= 5. These values are
guaranteed to be settled by the time user_active fires.
Because user_active fires repeatedly, the campaign will re-fire on
every heartbeat as long as the condition is true. How you cap it depends on
whether the condition can ever become false again:
Open-ended threshold
>= X on a lifetime counter
(purchase_count_lifetime >= 3,
completed_redemption_count_lifetime >= 5) โ once crossed, this
condition is true forever. The only valid gate is
once per user lifetime. A 30-day cooldown is wrong here: after
30 days the player still has โฅ 3 lifetime deposits and the campaign fires again.
Bounded range
>= X AND <= Y
(purchase_count_lifetime >= 3 AND <= 6) โ the player
eventually exits the range (once they exceed Y), so the condition becomes false
on its own. Here a time-based cooldown (e.g. once per 7 days) can work, but
confirm the business intent: do you want the campaign for every player who passes
through that range, or only the first time? If "first time ever," use a lifetime
gate regardless.
Follow this decision table every time you design a new event-triggered campaign.
Your condition uses only the event's own fields (not DWH aggregates).
Examples: deposit_in_usd > 50, is_first_time_deposit = true,
game_id = "crash", bonus_type = "freeSpins",
sc_balance < 10. These fields are set at the moment the event fires โ
no lag.
user_active
Your condition relies on any DWH aggregate count or amount โ
purchase_count_*, bet_amount_*, win_amount_*,
completed_redemption_count_*, bonus_claimed_*. These fields
read from ClickHouse and can lag ~30s behind the triggering action. Always pair with a
per-user frequency cap.
You want to trigger on the Nth occurrence of the event itself.
Optimove counts occurrences internally from when the player enters the trigger โ no DWH
involved. This is reliable and does not need user_active. For a time window
(e.g. "3 bets within 1 hour"), add the event multiple times in the trigger sequence and
set the timeframe at the bottom โ a single "Occurred N times" condition has no time window.
user_active
user_activepurchase_count_lifetime >= 3user_activebet_amount_30d >= 1000user_activecompleted_redemption_count_lifetime >= 5redemption_completed
user_activebonus_claimed_lifetime >= 50depositis_first_time_deposit = truebet_finishedsc_balance < 10sc_balance is live, not DWH โ safe to use on action
eventOrganized by type: triggered (active players, real-time) and scheduled (inactive players). Every triggered scenario fires from a live event โ conditions are checked on the payload, not in the target group.
deposit fires the moment a purchase completes
Existing/New (visitor) user ยท not banned ยท opted in ยท Country = US
purchase_amount = $10
In-app popup or push: special $20 offer with extra SC bonus
bet_finished fires after every bet resolves
Existing user ยท not banned ยท opted in ยท avg_purchase between $4.99โ$9.99 (โฅ5 purchases)
sc_balance < 1
Top-up offer personalised to their typical spend tier
redemption_requested fires when cashout is submitted
Existing/New (visitor) user ยท not banned ยท opted in
sc_balance below 2 SC after the request
Purchase incentive โ "top up while your cashout processes"
bonus_claim fires the moment the bonus is claimed
Existing/New (visitor) user ยท not banned ยท opted in
risk_score < 70 โ exclude bonus abusers from receiving further incentives
Engagement campaign โ "make the most of your bonus coins"
bet_finished fires after every bet
Existing/New (visitor) user ยท not banned ยท opted in
sc_balance < 1 AND risk_score < 70
Top-up offer โ not shown to players with high risk or abuse signals
deposit fires on every purchase
Existing/New (Visitor) users ยท not banned ยท opted in
is_first_time_deposit = true
First purchase welcome campaign with bonus coins or special pack
level_change fires on every level-up
Existing/New (visitor) user ยท not banned ยท opted in
No extra condition needed โ every level-up is worth celebrating
Congratulations popup + preview of next level perks and XP threshold
bet_finished fires after every bet
Existing/New (visitor) user ยท not banned ยท opted in
xp_to_next_level < 100 (threshold to confirm with product)
"You're only X XP away from [next level name]!" โ motivation push or popup
bet_finished fires after every bet
Existing/New (visitor) user ยท not banned ยท opted in
is_winning_bet = true AND amount_won_in_usd > 50
"Congrats on your big win! Ready to ride the streak?" โ offer to keep playing
redemption_completed fires when cashout is approved and paid
Existing/New (visitor) user ยท not banned ยท opted in
sc_balance < 5 โ focus on players who have little left after cashing out
"Your cashout is on its way! Here's a bonus pack to keep the fun going"
deposit โ configured as Repeated
Existing/New (visitor) user ยท not banned ยท opted in
deposit ร 4 in trigger sequence + 24h timeframe at bottom
VIP loyalty reward โ bonus coins or exclusive pack for frequent buyers
bet_finished โ configured as Repeated
Existing/New (visitor) user ยท not banned ยท opted in
bet_finished ร 3 in trigger sequence + 60 min timeframe, risk_score < 70
"You're on a roll! Here's a bonus bet to keep it going"
Event A: sign_up ยท Event B expected: deposit ยท Window: 30 minutes
Visitors (new users today) ยท not banned ยท opted in
If deposit does NOT fire within 30 min after sign_up โ campaign fires
"Welcome! Your first purchase comes with X bonus coins โ offer expires in 1 hour"
Event A: deposit ยท Event B expected: bet_finished ยท Window: 15 minutes
All users ยท not banned ยท opted in
If bet_finished does NOT fire within 15 min after deposit โ campaign fires
"You've got coins! Try [featured game] โ your first bet is waiting"
cashier_exit fires when user closes the cashier without completing a purchase
All users ยท not banned ยท opted in
Option A โ Simple (immediate)
None โ event already signals abandonment. Campaign fires immediately on cashier_exit.
Popup or push โ player is still in session
Option B โ Uncompleted Sequence (give them a chance first)
Event A: cashier_exit ยท Event B expected:
deposit ยท Window: 10โ15 min
If no deposit follows โ campaign fires
Push or email โ player may have already left the session
Scheduled campaigns run on batch data (nightly snapshot) and can reach inactive players. Unlike triggered campaigns, Target Groups can include richer filters here โ the batch data is fresh enough for players who haven't been active recently, and stale-data risk is near zero for that group.
No trigger event โ scheduled campaign sent at a fixed time
Existing user ยท not banned ยท opted in ยท Days Since Last Login โฅ 7 AND โค 30 ยท Total Purchase Amount, Lifetime > $10
No trigger condition โ TG filters define the audience
Email at 5 AM: "We miss you! Here's 500 free GC to come back"
No trigger event โ scheduled campaign sent daily
Existing user ยท not banned ยท opted in ยท Churn Probability Score > 70 ยท Days Since Last Activity โฅ 3 ยท registered more than a few days ago
No trigger condition โ TG filters define the audience
Email at 11 AM: personalised retention offer based on their favorite game or spend tier
These scenarios use DWH aggregate fields now embedded in every event payload. All windowed metrics are grouped by UTC calendar day โ not rolling windows. "Today" means current UTC day, "7d" means last 7 calendar days from UTC midnight.
All DWH aggregate fields (bet_amount_today, win_amount_7d, purchase_count_lifetime, etc.) are
sourced from ClickHouse and can lag ~30 seconds behind the actual event that triggered
them.
deposit event fires instantly โ but purchase_count_lifetime inside that same
event payload may still read 2 because the DWH hasn't processed the transaction yet.
The condition purchase_count_lifetime >= 3 fails. The campaign silently misses.
On other occasions the DWH may have already updated โ making the trigger non-deterministic.
user_active
as the trigger instead of the action event.
user_active fires every ~2 minutes while the player is active. By then the DWH has settled.
Scenarios 18 and 20 below use user_active for exactly this reason.>= X on lifetime counters) gate
once per user lifetime โ the condition never becomes false again. For bounded ranges
(>= X AND <= Y) a time-based cooldown is acceptable since the player will eventually
exceed Y and exit the range naturally.
user_active fires every ~2 min while player is active
Existing user ยท not banned ยท opted in
bet_amount_today โฅ X SC (threshold to be defined with product)
Loyalty reward or VIP offer โ "you've been on a roll today"
bet_amount_today is a DWH aggregate
(lags ~30s). Triggering on bet_finished risks reading a stale value. user_active
fires ~2 min after activity โ DWH settled by then. Gate: once per UTC day per player โ
bet_amount_today resets at UTC midnight so a daily cap matches the window.
user_active fires every ~2 min while player is active
Existing/New (visitor) user ยท not banned ยท opted in
sw_package_type = "standard" โ only fires for standard pack purchases, not promo or premium
In-app popup โ "Upgrade to the premium pack and get 2ร the SC for just $X more"
is_first_time_deposit = false to exclude first-time buyers โ they have a
separate welcome campaign (Scenario 06).
user_active Fires every ~2 minutes while player is active in session
Existing user ยท not banned ยท opted in
purchase_count_lifetime >= 10 โ exact milestone, reliable because user_active fires after DWH settles
"Thank you for your 10th purchase! Here's a loyalty bonus as a thank you"
purchase_count_lifetime is a DWH aggregate;
user_active guarantees it has settled before the condition is evaluated. Gate: once
per user lifetime. The threshold >= 10 is true forever once crossed โ a
time-based cap would re-fire the campaign every period indefinitely.
Phase 1 (v3.73โ3.76) โ new events (redemption_requested, redemption_completed,
bonus_claim), Postgres enrichment on all events (sc_balance, risk_score, xp_to_next_level,
is_first_time_deposit, sw_package_type, affiliate_id, etc.). Covers scenarios 1โ14, 16โ17.
Phase 4 (v3.73) โ cashier_exit frontend event. Covers scenario 15.
Phase 2 (v3.74) โ DWH aggregate enrichment on all events (bet/win/purchase amounts, bonus
claimed, login count, redemption rate, net purchase). Adds user_active heartbeat event. Unlocks scenarios
18โ20. โ ๏ธ All DWH aggregate values lag ~30s โ design trigger conditions accordingly.
The two channels serve different players at different moments.
| Topic | Batch / Scheduled | Events / Triggered |
|---|---|---|
| Who it targets | All players, including inactive | Primarily active players โ but backend-triggered events (e.g. redemption_completed) can reach any player |
| Data freshness | Yesterday's snapshot | The moment it happened |
| Update frequency | Once per night | Every action, every time |
| Campaign timing | Scheduled (e.g. 5 AM) | Instant (seconds after action) |
| Best use case | Reactivation, win-back, loyalty | Upsell, celebration, follow-up |
| Do they affect each other? | No. Completely independent channels. Events never update batch data. | |
| Channel type | Batch | Events |