Earning points
How to use it
Decide how customers earn, then let it run automatically:
- Set your base earn rate — e.g. 10 points per $1 spent. (Loyalty → Setup → Earning)
- Set tier multipliers so bigger spenders earn faster. (Loyalty → Tiers)
- Add earning beyond purchases — sign-up, reviews, birthdays. (Loyalty → Settings → Actions)
- Run a campaign for a push, like a double-points weekend. (Loyalty → Campaigns)
Earning then happens automatically on every paid order — no manual work.
⏱ ~10 min · 💳 Starter+ · 🎯 Points awarded on the next paid order
Purchase earning
When an order is marked paid, the customer earns:
points = round( order_total × base_rate ) × tier_multiplier
With defaults (base rate 10 points per unit of currency), a $80 order earns:
| Tier | Multiplier | Points on an $80 order |
|---|---|---|
| Bronze | 1.0× | 800 |
| Silver | 1.5× | 1,200 |
| Gold | 2.0× | 1,600 |
| Platinum | 3.0× | 2,400 |
The tier used is the customer's tier at the time of the order, derived from
their lifetime points. The award is written as an earned ledger row keyed
to the order, and a uniqueness rule on (shop, order, type) means a duplicate
webhook delivery can never award the same order twice.
The earn math lives in a single pure module that both the award engine and the
merchant-facing preview card import. So the "customers earn N points" number a
shopper sees on the product page is computed by the exact same code that the
ORDERS_PAID webhook later runs — they cannot disagree.
Stacking bonus earning
On top of the base purchase award, an order can trigger extra earning. Each is
written as its own bonus ledger row so the reason reads honestly and each
is independently idempotent.
Campaigns — time-boxed multipliers or flat bonuses
Run a "Double Points Weekend" or a "+200 points on this collection" push. A
campaign has a type (multiplier or bonus), a scope (store or specific
products), and a start/end window. The single best active campaign for the
order applies.
| Field | Meaning |
|---|---|
| Type | multiplier (e.g. 2× points) or bonus (flat extra points) |
| Scope | Whole store, or only orders containing specific products |
| Window | startsAt → endsAt; status auto-advances scheduled → active → ended |
Milestones — one-time lifetime rewards
Gamify long-term loyalty: award a one-time bonus the first time a customer's lifetime points cross a threshold (e.g. "10,000 points → 500 bonus"). Each milestone grants once, ever — checked against the new lifetime total after the order's base + campaign points.
Purchase-threshold bonus — revenue-positive
A flat extra award when an order's qualifying value clears a floor (e.g. "orders over $150 earn a 500-point bonus"). Unlike a check-in reward, the bonus is tied to a real qualifying purchase, so it's genuinely revenue-driven. Off by default.
Action points — earning beyond purchases
So loyalty isn't only a repeat-purchase discount, customers earn for engagement and acquisition actions. Each action grants once (de-duped on the action's natural scope), respects the programme's active status, and is configurable (0 disables an action).
| Action | Default | Trigger | One-per |
|---|---|---|---|
| Sign-up | 200 | Account created (CUSTOMERS_CREATE) | Account |
| Verified review | 50 | Review of a purchased product | Product |
| Birthday | 500 | Daily cron matches month/day | Year |
| Referral | 100 | Friend places first paid order | Handled by the referral engine |
Reviews are gated to verified purchases — a customer can only earn the review bonus for a product they actually bought (recorded from order line items). Details on check-in, birthday, and reviews in Engagement & streaks.
What a customer's ledger looks like
+ earned 800 Purchase: order #1042 — bronze tier (1.0×) bal 800
+ bonus 800 Campaign: Double Points Weekend bal 1,600
+ bonus 500 Milestone reached: 1,000 points bal 2,100
- redeemed −500 Redemption — code LOY-7QK2 (5.00 off) bal 1,600
- expired −120 Points expired after 12 months bal 1,480
Every row carries the before/after balance and a human reason, so support can explain any balance and finance can audit the liability.
Points expiry
Earned and bonus points expire after the configured window (default 12 months; set to 0 to never expire). Expiry is stamped on each earn lot at award time, and a monthly sweep reclaims lots whose window has passed — oldest-first, clamped to the customer's live balance so a spent-down customer never goes negative. Customers with points expiring soon are a prime re-engagement audience.
Best practices
✅ Set your earn rate against your redeem rate deliberately. With the defaults (10 pts per $1, 100 pts = $1) each dollar spent returns ~10% in points value at the base tier — generous. Tune the base rate to your target reward cost.
✅ Use campaigns for pushes, not permanent lifts. A time-boxed double-points weekend drives urgency; a permanent 2× just inflates liability.
✅ Turn on the purchase-threshold bonus to lift AOV, not the check-in reward — the threshold bonus only pays out on real, qualifying revenue.
❌ Don't disable expiry unless you've modelled the liability. "Never expire" means points accumulate forever on your books.