Skip to content
ESC

Searching...

Quick Links

Type to search • Press to navigate • Enter to select

Keep typing to search...

No results found

No documentation matches ""

Events and member synchronization.

The full 32-event catalog, grouped by kind, plus how to keep your own member records in sync.

Jul 11, 2026

An event is one thing that happened in Reward Loyalty: a member earned points, a stamp card filled up, a voucher got used. You choose which events an endpoint receives when you add it; this page lists everything on offer, what each one carries, and how to use the member data every event carries to keep your own records current.

The event catalog

You can subscribe an endpoint to any of 32 events, organized in seven groups. The badge on each event tells you what kind of fact it reports:

Badge Meaning
Broad Covers every change of one kind, such as any points increase. Good for balance mirrors.
Detailed One specific committed action, such as a purchase or a void. Good for automation.
Lifecycle The platform commits the change itself, from a schedule or a rule: expiry sweeps, tier movement, automatic voucher gifts.
Privacy A deletion notice, with stricter payload retention.

A ping event also exists, sent only by the Send ping button. It is a connection test, never a subscription choice.

Points and rewards

Purchases, point changes, and reward redemptions.

Event Key Badge Sent when
A member earns points on a purchase. points.earned Broad A member's point balance increases, such as a purchase, a bonus, or a received transfer.
Points are deducted from a member, such as a redemption. points.spent Broad A member's point balance decreases, such as a reward redemption, a sent transfer, or a correction.
A purchase is recorded for a member. purchase.recorded Detailed One accepted purchase is recorded, at the counter, through the API, or by a connected store. Never sent for points-only credits.
A member's points are adjusted manually. points.adjusted Detailed Staff or an API credits or deducts points directly, outside purchases, rewards, and transfers.
Awarded points are reversed. points.reversed Detailed A void or a refund removes previously awarded points from a member.
Points expire on a member's card. points.expired Lifecycle The daily sweep expires a member's unused points on one card.
A member transfers points to another member. points.transferred Detailed Once per transfer, with both the sender and the receiver. The matching balance events are sent separately.
A member redeems a points code. points.code_redeemed Detailed A member redeems a points code and the points are credited. The code value itself is never included.
A member redeems a reward with points. reward.redeemed Detailed A points reward redemption is recorded for a member.

Stamps

Stamps earned, corrections, and stamp card rewards.

Event Key Badge Sent when
A stamp is added to a stamp card. stamp.added Detailed A member earns one or more stamps on a stamp card.
A member completes a stamp card. stamp_card.completed Detailed A member fills the last slot of a stamp card.
A member redeems a stamp card reward. stamp.reward_redeemed Detailed A member redeems a completed stamp card reward.
A member's stamps are adjusted manually. stamp.adjusted Detailed Staff corrects a member's stamp count directly, up or down.
A stamp transaction is voided. stamp.voided Detailed A previous stamp transaction is reversed by a correction.
Stamps expire on a member's stamp card. stamp.expired Lifecycle The daily process expires a member's unused stamps on one stamp card.

Vouchers

Vouchers claimed, issued, redeemed, and voided.

Event Key Badge Sent when
A member redeems a voucher. voucher.redeemed Detailed A voucher redemption is recorded for a member.
A member adds a voucher to their wallet. voucher.claimed Detailed A member adds an existing voucher to their wallet, through a claim link, a save action, or a wallet pass.
A voucher is issued to a member automatically. voucher.issued Lifecycle A voucher is created or assigned for one member automatically, such as a tier reward or a birthday gift.
A voucher redemption is voided. voucher.redemption_voided Detailed A recorded voucher redemption is voided and the use is restored.
A voucher reaches its total usage limit. voucher.exhausted Lifecycle A voucher reaches its total usage limit and can no longer be redeemed.

Passes

Prepaid passes issued, used, and expired.

Event Key Badge Sent when
A prepaid pass is issued to a member. pass.issued Detailed A prepaid pass is issued to a member.
A member uses a visit on a prepaid pass. pass.used Detailed One or more visits are deducted from a member's prepaid pass.
A prepaid pass visit is voided. pass.use_voided Detailed A recorded visit is voided and returned to a member's prepaid pass.
A prepaid pass is fully used. pass.used_up Lifecycle The final visit of a prepaid pass is used.
A prepaid pass expires. pass.expired Lifecycle The daily process closes a prepaid pass whose validity period has ended.

Members and tiers

Members joining, leaving, and changing tiers.

Event Key Badge Sent when
A member enrolls in a card or stamp card. member.enrolled Detailed A member joins a card or stamp card for the first time.
A member leaves a card or stamp card. member.unenrolled Detailed A member removes a card or stamp card from their wallet. Never sent for privacy deletions.
A member's tier changes. member.tier_changed Lifecycle A member receives a first tier, an upgrade, or a downgrade. Contains tier IDs and levels, never tier names.

See Member synchronization below for how these three fit together with the rest of the catalog.

Member cards

Physical member cards you issue: created, claimed, and disabled.

Event Key Badge Sent when
A physical member card is issued. member_card.issued Detailed A physical card you issued is created, alone or as part of a batch. The card number is never included.
A member claims a physical member card. member_card.claimed Detailed A member links a physical card you issued to their account. The card number is never included.
A physical member card is disabled. member_card.disabled Detailed A physical card you issued is disabled. The reason is a fixed code; the card number is never included.

These events cover cards issued from your own card studio. Cards issued by the platform operator or created by members themselves send no webhook, because no single business owns those.

Privacy

Deletion notices, so your systems can remove their copy too.

Event Key Badge Sent when
A member deletes their data for your business. member.relationship_removed Privacy A member's data for your business has been deleted, so your systems can delete their copy too.

See Personal data and deletion notices for what this event obliges your systems to do.

Member synchronization

Most integrations built on these events need their own copy of who a member is. Reward Loyalty gives you two tools for that: an explicit enrollment event for the moment someone joins a card, and a member fragment carried on nearly every other event, so you never have to wait for enrollment to hear about someone new.

member.enrolled fires the first time a member is explicitly enrolled on a card or stamp card: when the member joins it themselves (scanning a QR code, or following from their wallet), when staff credit a first purchase or add a first stamp on a card the member had not joined, or when a member adds a stamp card to Google Wallet before joining. It fires at most once per member per card, for a genuinely new attachment only, never for a member who already joined.

Staff crediting a first purchase does enroll the member and does raise member.enrolled. Within that one visit, the events are created in this order: the purchase is recorded first, raising purchase.recorded and points.earned, and the enrollment check runs afterward, raising member.enrolled. That does not make member.enrolled a guaranteed second event to wait for: delivery order is not guaranteed (see below), so build your integration to upsert from whichever event arrives, not to depend on member.enrolled following or leading any other event. A few other attachments happen as a side effect of another action and never raise a separate member.enrolled at all: adding a loyalty card to Google Wallet, a WooCommerce or Shopify order, an Agent or REST API follow, or a voucher bonus. Each of those surfaces as its own event instead, for example points.earned or stamp.added. To catch every attachment, subscribe to those events as well, not member.enrolled alone.

Do not use points.earned to detect a new member. It fires on every purchase that earns points, not only the first, so it tells you a balance changed, never that someone is new.

The two membership events are not symmetric. member.unenrolled fires every time a member removes the card from their wallet, while member.enrolled fires at most once per member per card. A member who leaves and later re-adds the same card raises no second member.enrolled, so over time your system can receive two member.unenrolled deliveries against one member.enrolled. Treat member.unenrolled as the current state, never as a counter to balance.

Nearly every event carries a member fragment, not just member.enrolled. Delivery order across different event types is not guaranteed, even within one operation: the example above shows a genuine enrollment landing after the purchase that triggered it, and a member's first delivery to reach you could just as easily be points.earned, voucher.claimed, or any other event that happens to fire first. Build your sync to upsert (create-or-update) your own copy of the member record from the member fragment of whichever event arrives first, keyed on member.id, rather than waiting for member.enrolled or assuming it leads. member.enrolled stays useful for what it actually signals, an explicit loyalty or stamp card enrollment, not a universal "member created" event, and some members will never trigger one at all.

Choosing events for your program type

The event picker only offers groups and events your business can currently use. A stamp-only business never sees the Passes group, for example, because there is nothing there to subscribe to:

Events Requires
Points and rewards group, and member.tier_changed Loyalty cards
Stamps group Stamp cards
Vouchers group Vouchers
Passes group Prepaid passes
Member cards group Member cards (your own card studio)
member.enrolled, member.unenrolled Loyalty cards or stamp cards, either is enough
Privacy group (member.relationship_removed) Always available

If you subscribe an event while your business has the matching card type, then later stop using that card type, the subscription does not silently disappear. It stays active on that endpoint, still delivering, with a quiet Not in your plan badge in the picker so you remember why it is there. It stops only when you remove it yourself. A brand new endpoint can never start with an event outside what your business currently uses, and an existing endpoint can never gain a new one it did not already have.

Broad and detailed events together

One action at the counter can commit more than one loyalty fact. The broad events, points.earned and points.spent, report every balance change of their kind; the detailed events name the specific action. Selecting both is valid and common: the broad event keeps a balance mirror simple, the detailed event drives automation. Nothing is suppressed; each subscribed event arrives as its own delivery.

One operation Events that may be sent
Purchase with points purchase.recorded, points.earned; an enrollment bonus can add a second points.earned in the same operation
Staff void or store refund points.reversed, points.spent
Manual or API points-only adjustment points.adjusted, then points.earned or points.spent depending on the direction
Point transfer One points.transferred, one sender points.spent, one receiver points.earned
Points code redemption points.code_redeemed, points.earned
Points reward redemption reward.redeemed, points.spent
Final voucher use voucher.redeemed, then voucher.exhausted if the total limit is reached
Final pass use pass.used, then pass.used_up
Stamp card completion with a points reward stamp.added, stamp_card.completed, and the resulting points.earned

Every event from one operation shares one correlation_id. Each event has its own event_id, and each delivery to each endpoint has its own id. Here is one purchase arriving as two correlated deliveries:

{
  "id": "0199b7c2-4d5e-7f60-8a1b-2c3d4e5f6a7b",
  "event_id": "7d3f2a68-9c1e-4b5a-8f2d-0e6a4c8b2d19",
  "event": "purchase.recorded",
  "schema_version": 1,
  "created_at": "2026-07-10T14:32:05+00:00",
  "occurred_at": "2026-07-10T14:32:03Z",
  "partner_id": "b4a4e2b0-8f4e-4d0a-9a3f-1c2d3e4f5a6b",
  "correlation_id": "3f8c9d0e-1a2b-4c3d-8e5f-6a7b8c9d0e1f",
  "test": false,
  "data": {
    "member": {
      "id": "ee7f0d16-b176-44c5-9dd2-ee99bbc433e4",
      "unique_identifier": "344-319-665-971"
    },
    "card_id": "5b8d32c2-2b04-4033-a2dc-f750d762de88",
    "transaction_id": "e855986e-744c-4418-a4b8-6afbf7c04323",
    "purchase_amount_minor": 1250,
    "currency": "EUR",
    "points_awarded": 50,
    "source": "staff",
    "business_time": {
      "timezone": "Europe/Amsterdam",
      "local_date": "2026-07-10"
    }
  }
}
{
  "id": "0199b7c2-4d5e-7f60-8a1b-5e6f7a8b9c0d",
  "event_id": "9a5b3c71-2d4e-4f6a-9b8c-1d0e2f3a4b5c",
  "event": "points.earned",
  "schema_version": 1,
  "created_at": "2026-07-10T14:32:05+00:00",
  "occurred_at": "2026-07-10T14:32:03Z",
  "partner_id": "b4a4e2b0-8f4e-4d0a-9a3f-1c2d3e4f5a6b",
  "correlation_id": "3f8c9d0e-1a2b-4c3d-8e5f-6a7b8c9d0e1f",
  "test": false,
  "data": {
    "member": {
      "id": "ee7f0d16-b176-44c5-9dd2-ee99bbc433e4",
      "unique_identifier": "344-319-665-971"
    },
    "card_id": "5b8d32c2-2b04-4033-a2dc-f750d762de88",
    "points": 50,
    "purchase_amount": 1250,
    "transaction_id": "e855986e-744c-4418-a4b8-6afbf7c04323",
    "ledger_event": "staff_credited_points_for_purchase",
    "source": "staff",
    "balance_after": 150,
    "purchase_amount_minor": 1250,
    "currency": "EUR"
  }
}

Both carry the same correlation_id and the same transaction_id, so a consumer that receives both knows they describe one sale.

The envelope and headers

Every delivery is a POST request whose JSON body wraps the event data in a fixed envelope:

Field Meaning
id The delivery id, unique per delivery to one endpoint and stable across retries. The same value rides in the X-RewardLoyalty-Delivery header. Use it to skip duplicates.
event_id The business fact's id. Every endpoint receiving the same fact gets the same event_id. Use it to correlate across endpoints.
event The event key, for example purchase.recorded.
schema_version The payload schema version, 1 today. Fields are only added, never removed or renamed; a change that breaks that promise would raise this number.
created_at When the delivery record was created, ISO 8601 with a numeric UTC offset, for example 2026-07-10T14:32:05+00:00. This field predates the rest of the envelope and keeps its original format so older consumers keep parsing it.
occurred_at When the loyalty fact was committed, UTC with a trailing Z, for example 2026-07-10T14:32:03Z. Use this one for timelines and ordering.
partner_id Your partner id.
correlation_id Shared by every event from one operation. null when no useful correlation exists.
test true only for a ping or a sample send, never for a production event.
data The event's own fields, described per event below.

Six headers ride along with every request:

Header Value
Content-Type application/json
X-RewardLoyalty-Event The event key, for example points.earned
X-RewardLoyalty-Delivery The delivery id, identical to id in the body
X-RewardLoyalty-Event-Id The business fact's id, identical to event_id in the body
X-RewardLoyalty-Schema-Version The payload schema version, identical to schema_version in the body
X-RewardLoyalty-Signature sha256= followed by the base64-encoded HMAC-SHA256 of the raw request body, signed with the endpoint's own secret. See Security and signature verification.

The headers mirror body fields for convenience; the signed JSON body is the authoritative copy.

A field declared for an event is always present. When a value does not apply, or an older record never stored it, the field is JSON null; a declared field never disappears from the payload.

The member object

Every member field (and the sender and receiver fields on a transfer) carries exactly two values:

{ "id": "ee7f0d16-b176-44c5-9dd2-ee99bbc433e4", "unique_identifier": "344-319-665-971" }

id is the member's internal id and unique_identifier the stable code staff and APIs use to find them. No name, email address, or other profile detail rides in a webhook payload. Look a member up through the REST API or the Agent API when a delivery needs to show more; APIs write, webhooks notify.

Money fields

Money in a webhook is always a pair: an integer field ending in _minor and an uppercase ISO 4217 currency code. The integer counts the currency's smallest unit, so the number of decimal places depends on the currency, never on the payload:

Payload Currency rule Amount
"purchase_amount_minor": 1250, "currency": "EUR" Euro has 2 fraction digits 12.50 EUR
"purchase_amount_minor": 1250, "currency": "JPY" Yen has 0 fraction digits 1250 JPY
"purchase_amount_minor": 1250, "currency": "BHD" Bahraini dinar has 3 fraction digits 1.250 BHD

Never assume two fraction digits and never parse a formatted string; look up the fraction digits for the delivered currency code. Point counts, stamp counts, and visit counts are plain integers. A signed change ends in _delta; a named quantity such as points_expired is never negative.

The older purchase_amount field on points.earned and points.spent also carries integer minor units and stays for backward compatibility. Prefer purchase_amount_minor with currency in new integrations.

Timestamps and business time

Every exact instant in a payload is a UTC ISO 8601 string with a trailing Z, such as expired_at, claimed_at, or the envelope's occurred_at. Store and compare those as they are.

Events driven by a calendar rule (a purchase schedule, an expiry, a validity window, a birthday gift) also carry a business_time object:

"business_time": {
  "timezone": "Europe/Amsterdam",
  "local_date": "2026-07-10"
}

timezone is the owning business's IANA time zone and local_date the calendar date of the event in that zone. This answers "which business day was that?" without you re-implementing time zone math, including across daylight saving changes. Two examples on the night Amsterdam leaves summer time (October 25, 2026):

  • occurred_at: 2026-10-24T22:30:00Z is 00:30 local, so local_date is 2026-10-25 while the UTC date still reads October 24.
  • occurred_at: 2026-10-25T01:30:00Z is 02:30 local, after the clocks fell back, and local_date is again 2026-10-25.

That local day is 25 hours long, and both deliveries land on it. Group reports by local_date when you want business days; order and deduplicate by occurred_at, which never repeats or jumps.

Event payloads

Every event's exact fields are listed in the Webhook event field reference, one table per event with a generated example for each group. Turn there when you need the precise shape of an event's data block; the catalog above tells you which events exist and when they fire.

Cookies on this site.

Google Analytics runs only if you allow it. Cookie policy