Earning rules

Decide how members earn points.

View as MarkdownInstall skills

Earning rules map events — a payment, a signup, a referral — to points awarded, with multipliers, caps, and eligibility windows. They are the configurable core of a loyalty program: when an event fires, Engagement evaluates every active rule and writes a points_transaction for each match.

Rule anatomyAsk

A rule attaches to a program, listens for one trigger, and computes points from an award expression. The optional fields shape when and how much.

Prop

Type

import { Vinr } from '@vinr/sdk';
const vinr = new Vinr({ secretKey: process.env.VINR_SECRET_KEY });

// Earn 1 point per €1 spent on any completed payment.
const rule = await vinr.loyalty.earningRules.create({
  program: 'prog_default',
  trigger: 'payment.completed',
  award: { type: 'per_amount', per: 100, points: 1 }, // per 100 minor units (= €1)
});                                                    // "rule_..."

The award.type controls the computation:

TypeMeaningExample
per_amountPoints per N minor units of amount.1 point per €1 → { per: 100, points: 1 }
fixedA flat award regardless of amount.Welcome bonus → { points: 500 }
per_currencyPer-currency rate, for multi-currency programs.{ EUR: 1, USD: 1, GBP: 1.2 }

Event triggersAsk

Rules react to events from across VINR. A single program can mix triggers — purchase points, a welcome bonus, and referral rewards are three rules, not three features.

TriggerTypical rule
payment.completedAward per €1 spent.
loyalty.account.createdWelcome bonus.
subscription.createdBonus for committing to an annual plan.
invoice.paidRecurring-billing loyalty.
referral.convertedReward referrer and referee.

For a payment to earn, it must be linked to a member. See Linking payments & loyalty for how customer and metadata resolve to a loyalty_account.

// A flat welcome bonus when a member enrolls.
await vinr.loyalty.earningRules.create({
  program: 'prog_default',
  trigger: 'loyalty.account.created',
  award: { type: 'fixed', points: 500 },
});

Multipliers & bonusesAsk

multiplier scales a rule's award. Multipliers compose: the effective rate is the rule multiplier times any active tier multiplier times any campaign multiplier in effect at the time of the event.

// Double points on the base purchase rule.
await vinr.loyalty.earningRules.update('rule_abc123', { multiplier: 2 });

A Gold member (tier multiplier 1.5) earning under a 2x rule during a 3x weekend campaign earns 1 × 2 × 1.5 × 3 = 9 points per €1. Multipliers stack multiplicatively, not additively.

Caps & limitsAsk

Caps protect against runaway awards from large or repeated transactions. A cap limits points per member within a rolling window.

// Cap purchase earnings at 5,000 points per member per calendar month.
await vinr.loyalty.earningRules.update('rule_abc123', {
  cap: { points: 5000 },
  window: 'month',
});

When a cap is hit, the resulting points_transaction is truncated to the remaining headroom and tagged capped: true in its metadata, so you can detect and communicate the limit. Set window: 'lifetime' for one-time bonuses you never want to award twice — for example, a welcome bonus fires once per member even if the trigger repeats.

Caps are evaluated at award time, not retroactively. Lowering a cap does not claw back points already granted; it only constrains future awards.

Testing rulesAsk

Use the sandbox to fire real events without moving money. Create a payment with a sandbox card, then inspect the member's points transactions.

Simulate a qualifying event

In sandbox, create a payment for a linked customer using test card 4242 4242 4242 4242. On completion it emits payment.completed.

Inspect the resulting transactions

List the member's points_transaction records and confirm the award, multiplier, and any cap behaved as configured.

const txns = await vinr.loyalty.pointsTransactions.list({
  account: 'loy_member123',
  limit: 5,
});
// Each ptx_ shows source rule, points, and metadata.capped if truncated.

Verify the earned webhook

Confirm your endpoint received loyalty.points.earned. Verify the signature before trusting the payload.

const event = vinr.webhooks.verify(payload, signature); // x-vinr-signature
if (event.type === 'loyalty.points.earned') {
  // event.data.points, event.data.account, event.data.rule
}

Next stepsAsk

Was this page helpful?
Edit on GitHub

Last updated on

On this page