Watch And Earn docs
v3.1.0
Live demoConsole demo Get help
● Grow · Campaigns and tests

Growth tools
push campaigns, A/B tests, promo codes.

Send scheduled push campaigns to a segment with an inbox copy, test two values of an economy setting on your real users, and hand out promo codes for points.

SegmentsA/B, FNV-1a hashPromo codes

01Push campaigns

Console → Push campaigns (managers and owners). A campaign has a title (up to 65 characters), a message (up to 240), a segment and a send time in UTC. Leave the time empty to send at the next run. You can schedule up to 60 days ahead.

  1. ScheduleSchedule campaign writes pushCampaigns/{id} with status scheduled. You can Cancel it while it is still scheduled.
  2. SendThe job sendPushCampaigns runs every 5 minutes. It picks up due campaigns, writes an inbox item for each user in the segment (when the inbox feature is on), and sends a push to each of their devices. Banned users are skipped. At most 50,000 users per campaign.
  3. ResultStatus becomes sent (or failed), with the number of users and messages sent. Invalid device tokens are removed.
Segment in the consoleWho
EveryoneAll users
VIP usersUsers with an active VIP subscription
Inactive for 7 daysLast activity more than 7 days ago. Uses users.lastActiveAt, which 3.0 users get only after their next activity in 3.1.
New users (last 7 days)Signed up in the last 7 days
Level at least…Level ≥ the number you enter (1 to 1000)
Users in a teamThe members of one team you pick in the Team field
Campaigns to one team

Choose Users in a team, then pick the team in the Team field. The list shows the teams from the Teams page as [TAG] name; while no team exists it says "No teams yet" and the campaign cannot be scheduled. The console stores the team's id on the campaign (teamId), and the table shows the segment as team [TAG] name. The backend sends the push and the inbox copy only to that team's members (firebase/functions/src/push.ts). A team campaign without a team id, for example one written to Firestore by hand, ends as failed with the error "team segment without teamId".

Campaign pushes go to device tokens that the app reports with reportDevice. The Send now to everyone button is the 3.0 behaviour: one push to the topic all, right away, with no campaign record and no inbox item. Platform setup for push (APNs on iOS) is on Push notifications.

02A/B experiments

Console → A/B experiments. One experiment per line:

economy.pointsPerAd|a=10|b=15|50/50|on

That is: key|a=value|b=value|split|on or off. Two variants, whole numbers within the setting's own limits, a split of two numbers adding up to 100, one experiment per key. Invalid lines are skipped and listed on the Status page.

Keys you can test (EXPERIMENT_KEYS in admin/src/manifest.ts):

  • Rewarded videos: economy.pointsPerAd, economy.adDailyCap, economy.adCooldownSeconds
  • Wheel and scratch: economy.spinDailyFree, economy.spinExtraViaAdDaily, economy.scratchDailyFree, economy.scratchExtraViaAdDaily
  • Quizzes: economy.quizPointsPerCorrect, economy.quizDailyCap
  • Referrals and offerwalls: economy.referralInviterPoints, economy.referralInviteePoints, economy.offerwallPointsPerUsd
  • Ads: ads.placements.interstitial.everyNthBreak, ads.placements.interstitial.minIntervalSeconds, ads.placements.rewardedInterstitial.pointsPerView
  • Games: games.memory.points, games.word.points

The hash rule

Each user always gets the same variant, and the app (what it shows) and the server (what it credits) compute it identically. From docs/CONTRACT-3.1.md §10.2:

h       = FNV-1a 32-bit over the UTF-8 bytes of (uid + key)   # basis 0x811C9DC5, prime 0x01000193, mod 2^32
weights = split, if it is positive whole numbers, one per variant; else equal weights
bucket  = h mod sum(weights)
variant = the first i with bucket < weights[0] + … + weights[i]

Test vectors, checked by app/test/ab_hash_test.dart and the Functions tests: fnv1a32("a") = 3826002220; h("user_1" + "economy.pointsPerAd") = 2441342267, bucket 67 of 50/50, so variant b; h("user_2" + "economy.pointsPerAd") = 389521236, bucket 36, so variant a.

Change both sides or neither

If you change the hash in the app, change firebase/functions/src/ab.ts the same way. Otherwise users see one number and are credited another.

Reading the result

The package assigns and applies variants. It does not report results per variant: there is no experiment report in the console. To compare variants, compute each user's variant with the rule above from the uid, then compare their activity, for example their ledger or users/{uid}/daily documents in Firestore. Switching an experiment off returns everyone to the normal setting.

03Promo codes

Console → Promo codes → New code: the code (for example WELCOME50, 3 to 32 characters A–Z 0–9 _ -), the points, the most uses across all users, and an optional expiry in UTC. Users enter it in the app (redeemPromo). Each user can redeem a code once. The table shows uses so far. Deactivate stops a code at once.

  • Promo points are ledger type promo. They do not count as earned, so they do not raise season XP, events, team points or the velocity check.
  • Use codes for launches, social posts or support goodwill. Never sell codes for money outside the stores: that would bypass the store's billing rules.