How score streaks work
A score streak campaign tracks three layered ideas: time windows (periods), consecutive activity (streaks), and cumulative score (milestones).Periods and loss conditions
Every time a customer earns a score point, the campaign opens a period window — a countdown timer during which the customer must send the next qualifying event to keep the streak alive. If the timer runs out before the next event, the cycle is lost: the streak resets to zero and a new cycle begins. The loss window is defined byloss.conditions[0]:
MINUTES, HOURS, DAYS, WEEKS, MONTHS.
Cycles
A cycle is one continuous run of activity without a loss. Each loss or reset starts a fresh cycle identified by a newcurrent_cycle_id. This means multiple cycles can exist in a single streak record over its lifetime, and the history events are stamped with the cycle they belong to.
Milestones
Rules in a score streak campaign define score thresholds that trigger rewards. When a customer’scurrent_score reaches or exceeds a milestone, that milestone’s effects (rewards) are granted once per milestone per cycle.
Scheduled resets
Optionally, a campaign can define a reset calendar that forcefully closes the active cycle on a schedule — daily, weekly, or monthly. This is independent of the loss condition: a customer with a perfect streak will still be reset on the configured date.Score streak lifecycle
| State | Description |
|---|---|
ACTIVE | Customer has an active streak in progress |
CLOSED | Campaign ended; streak is archived |
events JSON column:
| Type | Trigger |
|---|---|
GAIN | Qualifying event processed; score incremented |
MILESTONE | Score crossed a milestone threshold |
LOSS | Period expired without a qualifying event |
RESET | Scheduled reset fired |
MANUAL_RESET | Support agent triggered a manual reset |
EVENT_REVERSAL | A source event was reversed, rolling back the associated score gain |
Campaign configuration
| Field | Type | Description |
|---|---|---|
trigger | string | Event type that increments the score (e.g. "PURCHASE") |
privacy | "PRIVATE" | "PUBLIC" | Controls visibility in public-facing APIs |
loss.conditions[0].type | "NO_TRIGGER_EVENT_WITHIN" | Always this value |
loss.conditions[0].amount | number | How many units before the streak is lost |
loss.conditions[0].unit | "MINUTES" | "HOURS" | "DAYS" | "WEEKS" | "MONTHS" | Time unit |
reset.enabled | boolean | Whether scheduled resets are active |
reset.calendar.frequency | "DAILY" | "WEEKLY" | "MONTHLY" | Reset cadence |
reset.calendar.week_starts_on | 0–6 | First day of week (0 = Sunday). Required when WEEKLY |
reset.calendar.day_of_month | 1–31 | Day of month for reset. Required when MONTHLY. Clamped to last day for short months |
Milestone rules
Each rule in a score streak campaign must have amilestone score value. When the customer’s score reaches that value within a cycle, the rule’s effects are applied. Multiple rules can define different reward tiers:
- Conditions in milestone rules are ignored; only
milestoneandeffectsmatter. - When the score reaches milestone 10, only that rule fires — the lower milestones (1, 5) do not fire again for the same score.
- If a score crosses a milestone for the first time in a cycle, the reward is granted. If the same milestone is crossed again later in the same cycle (e.g. after a reversal), the reward is granted again.
Event reversal behavior
When a source event is reversed:- All
GAINandMILESTONEhistory events from that source are markedreversed_at. - An
EVENT_REVERSALhistory event is appended with the negative score delta. - If the reversed event was in the active cycle,
current_score,current_streak,period_score, andperiod_event_countare decremented. - The
highest_milestone_scoreis recalculated from surviving milestone events. - If the score drops below a previously reached milestone, re-triggering the qualifying event will grant the milestone reward again.
Manual admin reset
Support operators can force-reset a customer’s active streak cycle without waiting for the loss cron or a scheduled reset. This appends aMANUAL_RESET history event, starts a new current_cycle_id, and zeros score counters for the active cycle.
Endpoint (internal): POST /api/admin/score-streaks/reset
| Field | Description |
|---|---|
account_id | Account UUID |
campaign_id | Score streak campaign UUID |
customer_id | Customer external id |
reason | Audit reason stored in history (for example Support requested reset) |
actor_id | Optional operator id for the audit trail |
CRON_CHECKSUM header (same secret used by Masivo cron routes). This endpoint is not part of the public Storefront API.
Comparison with punch cards
| Score Streaks | Punch Cards | |
|---|---|---|
| Trigger | Any event type | Any event type |
| Progress unit | Score (unlimited) | Punches (capped) |
| Completion | Milestones (repeating) | Single completion |
| Time pressure | Loss window + optional reset | None |
| Event history | Full JSON log | None |
| Reversal | Score rollback | Standard wallet |
| Cron required | Yes (expire lost streaks) | No |