Core concepts

Wallets & Points

Every customer has a wallet that tracks their loyalty point balance. The wallet is backed by an append-only ledger that records every point movement with a full audit trail.


Wallet overview

FieldDescription
idUnique wallet ID
customer_idThe customer this wallet belongs to
balanceCurrent point balance (integer)
expiring_soonPoints expiring in the near future

A wallet is automatically created when a customer is registered. There is exactly one wallet per customer.


Ledger entries

The ledger is an immutable, append-only record of every point movement. Each entry contains:

FieldDescription
typeThe type of movement: earn, adjust, redeem, or expire
amountSigned integer: positive for credits, negative for debits
running_balanceThe wallet balance after this entry
reasonHuman-readable explanation
causer_typeWhat triggered this entry: a transaction, an admin user or an API client
causer_idID of the triggering entity
transaction_idAssociated transaction (for earn entries)
created_atTimestamp of the entry

Entry types

TypeDirectionTriggered by
earnPositiveTransaction submitted. Points calculated from earn rules
adjustPositive or negativeManual balance adjustment by admin or API
redeemNegativeCustomer redeems a reward
expireNegativePoints expired under the vendor's expiration policy

Immutable ledger

Ledger entries are never updated or deleted. The running_balance field on each entry provides a verifiable audit trail. To correct a mistake, create a new adjustment entry rather than modifying existing entries.


Point batches and expiration

Every earn creates a batch, and so does every manual adjustment that adds points. The batch carries an expiry date when the vendor has point expiration enabled and the plan includes it, and none when it does not. Switching expiration off, or moving to a plan without it, changes only new batches: a batch that already has an expiry date keeps it and still expires on that date. Each batch records:

FieldDescription
amountPoints in this batch
consumedPoints already used from this batch
expires_atWhen the remaining points in this batch expire

A redemption or a manual deduction spends the batch that expires soonest first, and a batch with no expiry date last. Earn order is not the same thing: you can change expiration_months or switch expiration off at any time, so a batch earned earlier can expire later than one earned after it. Spending the most perishable points first keeps a customer from losing points that a redemption could have used. When a batch does expire, the points still in it are deducted from the wallet and recorded as an expire ledger entry.

How expiration works

  1. Vendor enables expiration and sets expiration_months (e.g., 12 months); the plan must include expiration
  2. Each time points are earned or added by hand while expiration is enabled and the plan includes it, a batch is created with expires_at set to that date + expiration months
  3. A redemption or a manual deduction spends the batch closest to its expiry date first, so expiry only removes points the customer still holds
  4. A background job periodically checks for expired batches and creates expire entries

Manual adjustments

Administrators can manually adjust a wallet balance through the admin portal or the API. Adjustments create a new ledger entry with type adjust:

POST /api/v1/customers/{customer}/wallet/adjust
{
    "amount": 500,
    "reason": "Compensation for service issue",
    "idempotency_key": "goodwill-2024-0042"
}

Positive amounts add points in a new batch that expires like earned points. Negative amounts deduct points from the batch that expires first. The idempotency_key makes the call safe to retry: re-sending the same key for the same customer returns the original ledger entry instead of moving the balance twice.

See Wallet endpoints for the complete API reference.

Previous
Customers