Multi-axis earning: why a single loyalty point kills the programme before it ships

This is the second post in Hospitality Loyalty Brief. The first explained why you'd run a retention programme. This one explains how to design one that doesn't collapse six months after soft-launch.

The original sin: "one rouble equals one point"

Most hospitality operators copy the retail-and-banking pattern: every spent unit earns one point; accumulate enough — redeem a night. Clear, measurable, easy to pitch.

And catastrophically wrong for a hotel.

Three reasons, all about the physics of the hospitality business:

A stay is one transaction, but many behaviours. The guest doesn't just pay for a room — they stay three nights, eat in your restaurant, visit your spa, leave a review, refer friends. "One rouble = one point" sees only the final bill.

ADR varies 5-8× across segments. A studio at €60 and a suite at €380 — inside one programme. A linear scale turns the programme into segregation: a premium guest earns 6× more for the same behaviour, while a mass-market guest accrues points faster than their true LTV contribution.

Seasonality breaks accrual. A summer-peak resort: a guest in August earns 1 800 points for a week, in February only 400 for the same week. Same behaviour, different "income" inside the programme — feels arbitrary, not fair.

4-axis earning: what fires simultaneously

In TTE.Loyalty every transaction (PMS event) hits four independent axes at once. The guest doesn't pick "how to count" — the system writes to all of them.

Axis 1 · Monetary

Money spent on property. Segmented across 6 revenue categories: room / F&B / spa / ancillary / retail / meetings. Each has its own tier-multiplier — because F&B revenue costs less to deliver than room revenue. That's reality, and accrual should reflect it.

monetary_points = Σ(category_revenue × category_multiplier × tier_bonus)

Example: a Silver member stays 3 nights (room €260, F&B €45, spa €72). Multipliers: room=1.0, F&B=1.4, spa=1.6. Silver tier-bonus +15%. points = (260×1.0 + 45×1.4 + 72×1.6) × 1.15 ≈ 511.

Axis 2 · Volume

Number of nights and stays — not amount, but count. This is what builds habit. A guest who stays 12 times for one night is more valuable for retention than a guest with one 12-night trip at the same money-spend: more touches, more upsell windows, clearer preference signal.

volume_points = stays × stay_bonus + nights × night_bonus

Example: those 3 nights = 1 stay = 3 nights × 100 + 1 stay × 800 = 1 100 points.

Axis 3 · Frequency

Not "how much" — "how often". This axis rewards the return pattern: a guest who stays every quarter earns a frequency bonus that grows non-linearly with the streak.

frequency_points = days_since_last_stay < THRESHOLD ? streak_bonus(streak) : 0

Retail doesn't use this axis — because retail visit frequency is measured in days, hotel frequency in quarters. Without a frequency axis a programme can't tell a "loyal" guest from a "one-off big spender".

Axis 4 · Engagement

Actions not tied directly to a purchase: left a review, filled out their preferences profile, brought a friend through referral, shared a photo with your tag, answered a post-stay survey. This is the currency of future revenue — engagement behaviour correlates with repeat-rate at 0.6-0.7 in McKinsey hospitality data.

engagement_points = Σ(action × action_weight × decay_factor)

Example: a 5⭐ review with photo (+1 200), filled profile (+400), referral leading to an actual stay (+5 000). These 6 600 points aren't tied to any single transaction — they're the guest doing work for your hotel, and the programme should recognise that.

Tier qualification: 5 types, not one

Same logic for moving between tiers (Silver → Gold → Platinum). A single spend-threshold kills a programme around month 18: "premium one-off spender" guests stay stuck on Gold without moving, and loyal "frequent low-spenders" never reach Silver.

The alternative: qualify via any of 5 channels:

Spend-quals — total annual spend

Stay-quals — number of stays per year

Night-quals — number of nights per year

Composite-quals — weighted combination (spend × stays)

Behavioural-quals — for guests who cross an engagement-actions threshold

A guest advances to the next tier the moment they hit any of the five. The resort operator picks which three or four to enable — and that's just tenant-level config in the admin.

Why all of this fits in one transaction

The core architectural decision in TTE.Loyalty: all four axes and all five quals recompute inside a single DB transaction when the PMS webhook arrives with a new stay.

tenantTransaction(tenantId, async (tx) => {
const monetary = computeMonetaryAxis(stay, tx)
const volume = computeVolumeAxis(member, stay, tx)
const frequency = computeFrequencyAxis(member, stay, tx)
const engagement = computeEngagementAxis(member, tx)

await applyPoints(member, [monetary, volume, frequency, engagement], tx)
await recheckTierQuals(member, tx)
await writeAudit(tx, { actorType: 'system', action: 'earn' })
})

Which means: either every accrual and tier-check went through — or nothing did. No "half-earns" on failure. No guests "accrued in one system, tier in another".

What the operator gets

The programme reflects real economics. A high-engagement, low-spend guest gets recognition — because they'll do more for retention than a "premium one-off".

Tier-mix doesn't collapse. The distribution across levels stays diversified, and the programme feels fair across the whole guest spectrum.

The seasonality problem is solved. The frequency axis rewards off-season returns — the programme itself nudges guests towards the dates you need.

Audit-trail on everything. Each accrual is its own row in append-only audit_log with a reason. No more "where did these points come from".

Next

The next post will be on tier-design: how to calibrate thresholds so Silver guests don't "get stuck" and the tier distribution stays stable for 18+ months.

Subscribe to Hospitality Loyalty Brief — a weekly breakdown of retention programme architecture →