Skip to main content
Build a cashback credit card program with three named rule sets alongside the program’s automatic default set. Tracking rules update counters, earning rules choose one cashback rate, and maintenance rules reset monthly state.

What you’ll build

The end result is 4 rule-set resources, 8 rules, and 1 automation: the automatic default set plus 3 named sets.

Assumptions

This guide assumes you already have:
  • A program created
  • An asset linked to that program (CASHBACK_USD, scale 2, UNLIMITED, SIMPLE)
See the quickstart if you need help with setup.

Create the rule sets

Every program already has a default rule set in the first position. This example intentionally leaves it empty and creates three named sets because the rules have separate responsibilities:
Save the three returned IDs as TRACKING_RULE_SET_ID, EARNING_RULE_SET_ID, and MAINTENANCE_RULE_SET_ID. Each new set is appended after the ones that already exist. Because these are created in the order tracking, earning, maintenance, Scrip numbers the sets default = 1, tracking = 2, earning = 3, maintenance = 4. All applicable ACTIVE rules across all four sets participate. If you add an applicable ACTIVE rule to default, it runs before this walkthrough’s named sets because default holds the first position. Set position controls execution priority only.

Tracking set

Each rule request below assigns its rule via rule_set_id to one of the three named sets. Create the rules in the order shown; Scrip numbers them within each set as they are added, so creation order is execution order. stop_after_match on any rule is scoped to that rule’s containing set.

1. Track monthly spend

Every rule in this program needs to know how much the participant has spent this month. We track two counters: total spend (for the threshold check) and non-category spend (for the retroactive bonus calculation). monthly_spend tracks everything, for the $2,500 threshold:

2. Track non-category spend

monthly_base_spend tracks only non-category purchases. This is what the retroactive bonus pays out on.
Counter values in conditions are snapshots: they reflect the state before the current event’s actions execute. These rules increment the counters in the database, but later rules in the tracking set and every later set still see the pre-event values. This is important for threshold detection.

3. Retroactive bonus at $2,500

When a participant crosses the $2,500 monthly threshold, they should retroactively earn an extra 2% on their non-category spend so far. This makes up the difference between the 1% they already earned and the 3% high-spender rate.
The condition has two parts that make it fire exactly once per month:
  • snapshot < 2500: hasn’t crossed yet
  • (snapshot + event.amount) >= 2500: this event crosses it
The credit amount uses monthly_base_spend (not monthly_spend) so the retroactive bonus only applies to purchases that earned the 1% base rate. Dining and grocery purchases already earned their full 5%/3%. Paying an extra 2% on those would overshoot.
Do not use stop_after_match here. It would skip later rules in the tracking set. The earning set would still run, but future tracking rules in this set would not.

Earning set

The earning rules are mutually exclusive. A matching category or high-spender rule stops only the rest of this set. Tracking and maintenance remain independent.

4. Dining: 5%

MCC codes: 5812 (restaurants), 5813 (bars), 5814 (fast food). stop_after_match: true ensures a dining purchase earns 5% and only 5%. It skips the later grocery, high-spender, and base rules in the earning set. It does not stop another set.

5. Groceries: 3%

MCC codes: 5411 (grocery stores), 5422 (freezer/meat lockers). The same set-local stop prevents the high-spender and base rules from also firing.

6. High-spender: 3% on everything else

After crossing the $2,500 monthly threshold, all remaining non-category purchases earn 3% instead of 1%.
This uses (snapshot + event.amount) >= 2500 so the threshold-crossing purchase itself earns at the higher rate.

7. Base: 1% on everything else

The catch-all for purchases below the threshold that don’t match a category.
Only fires when no earlier rule in the earning set matched: the dining, grocery, and high-spender rules all use stop_after_match.

Maintenance set

8. Monthly counter reset

Triggered by the automation below. Subtracting the current value zeros both counters.

Automation

Create one automation to fire the monthly reset:
On the 1st of each month at midnight UTC, this fans out a monthly_reset event to every participant with a non-zero spend counter. The filter avoids unnecessary events for inactive participants.

Rule evaluation flow

Here is the effective execution sequence for a purchase. The empty default set contributes no rule evaluations in this walkthrough:
The named sets run after the empty default set. If default contained an applicable ACTIVE rule, that rule would participate first, before any named set. The tracking set then evaluates before the earning set. Its counter writes persist first, but earning conditions and dynamic action expressions still read the shared event-start snapshot. The earning rules are mutually exclusive through set-local stop_after_match. The maintenance set still participates after an earning rule stops its own set.

Example event

Your backend sends this when a card transaction settles:
Only type, amount, and mcc are used by rule conditions. Include whatever else you need for your own analytics.

Edge cases and watchouts

Rounding

With scale: 2, amounts are stored to the cent. Use round(expr, 2) in every CREDIT to avoid precision issues:

The threshold-crossing purchase

The event that pushes monthly_spend past $2,500 earns at the 3% rate (high-spender), not 1%. This is because high_spender_cashback checks (snapshot + event.amount) >= 2500, which is true for the crossing purchase. The retroactive bonus also fires on the same event, covering all prior spend.

Category purchases don’t care about the threshold

A dining purchase always earns 5% whether the participant has spent $500 or $5,000 this month. The stop_after_match on dining_cashback (the first rule in the earning set) fires before the high-spender and base rules in that set are evaluated. The threshold only affects non-category purchases.

The second counter

monthly_base_spend exists so the retroactive bonus only pays out on purchases that earned 1%. Without it, a participant who spent $2,000 on dining before crossing the threshold would get an extra 2% on those dining purchases, bumping them from 5% to 7%, which isn’t intended. The retroactive bonus should only upgrade 1% purchases to 3%.

Counter resets and tier status

On the 1st of each month, the counter drops to zero. There’s no “tier” to maintain: the high-spender rate is purely counter-driven. A participant who spent $10,000 last month starts fresh at 1% the next month.

Refund handling

This example doesn’t include refund rules. In production, you’d add rules to:
  1. Debit cashback earned on the refunded transaction
  2. Reduce the monthly_spend counter so the threshold status stays accurate
A refund rule might look like:
Your backend would include original_cashback and original_amount from the original transaction record. allow_negative lets the debit succeed even if the participant has already spent the cashback; without it, a DEBIT against an insufficient available balance fails and the refund event is marked FAILED.

Excluded transaction types

The base rule doesn’t exclude any MCC codes. In practice, you’d want to block non-qualifying transactions like cash advances or wire transfers:

Large MCC lists

The CEL in operator works well for short lists (5-20 entries). If you need to match against hundreds of merchants or categories, move the classification into your backend and pass a flag like event.is_dining: true in the event payload instead.