Loyalty
Implementing Stamp Cards & Claim Rules with the io.tt SDK
The Loyalty template enables product‑based stamp collection, allowing customers to scan product codes (QR/NFC) to earn "stamps" toward a reward.
The SDK exposes four Loyalty methods:
getItemClaimStatusregisterLoyaltyUserclaimLoyaltyItemgetStampHistory
This guide describes how to implement a complete loyalty card system, including per‑code claiming rules, scan cooldowns, and reward unlock behaviour. All experience rules (claim limits, cooldowns, number of stamps required) are configured in the io.tt dashboard.
How Loyalty Experiences Work
A Loyalty experience is fully configured inside the io.tt dashboard, including:
| Setting | Meaning |
|---|---|
| Number of Stamps | The number of stamps required to fill 1 card |
| Item Claim Limit | The number of users that may claim the same code |
| Rescan Limit | The number of times the same user can scan the same code |
| Rescan Time Throttle | The amount of time a user must wait before rescanning the same code |
| Card Claim Limit | The number of times the same user can start a new card |
| Card Time Throttle | The amount of time a user must wait before starting a new card |
These rules are enforced server‑side and returned through SDK responses.
Typical Loyalty Flow
- User scans a product → app gets
itemSlug - Check whether the code can be claimed →
getItemClaimStatus - If user not yet registered →
registerLoyaltyUser - Claim the stamp →
claimLoyaltyItem - Fetch updated stamp card →
getStampHistory - Render card progression
- If card complete → show configured reward
1. Initialise the SDK
Create a single SDK instance using your Experience API key. If you haven't set this up yet, see Getting Started.
import { IOTT } from "@io-tt/sdk";
const sdk = new IOTT({
apiKey: process.env.IOTT_API_KEY,
});2. Check Eligibility (getItemClaimStatus)
Before collecting any user details, check whether this specific product code is eligible to earn a stamp. This lets you surface an appropriate message early — for example, if the code has already been claimed by the maximum number of users — before asking the consumer to register or log in.
const status = await sdk.getItemClaimStatus(EXPERIENCE_ID, itemId);
// status.claimed — whether this code has already been claimedThe returned ItemClaimStatus object includes:
| Field | Type | Description |
|---|---|---|
id | string | Internal ID of the claim item |
createdAt | string | ISO 8601 timestamp of when the item was created |
claimed | boolean | Whether this code has already been claimed |
3. Register Loyalty User (registerLoyaltyUser)
If the user is not yet registered, register them before claiming. This creates a loyalty profile linked to their email address and returns their initial stamp history. Email is required for loyalty because it serves as the persistent identifier used to track stamps across multiple scans and sessions.
const history = await sdk.registerLoyaltyUser(
EXPERIENCE_ID,
{
itemSlug, // required
email, // required — email is required for loyalty and also serves as the
// identifier for `claimLoyaltyItem` and `getStampHistory`
// Personal details (all optional)
title,
firstName,
lastName,
gender, // 'male' | 'female' | 'other'
phoneNumber: { countryCode: 44, number: '7700900000' },
dateOfBirth, // ISO 8601 string, e.g. "1990-01-15"
// Address (all optional)
addressLine1,
addressLine2,
locality,
administrativeDivision,
postalCode,
country,
// Consent (all optional)
marketingConsent,
profilingConsent,
// Custom fields (optional)
extra: {
// additional custom fields
},
},
images, // optional: Array<File | Blob | Buffer>
);Only itemSlug and email are required. All other entry fields and the images argument are optional.
Registration data is automatically routed to your configured CRM. See the CRM Integration Guide for details on field mapping and how extra fields are handled.
Proof of Purchase
To attach structured proof-of-purchase data (e.g. receipt fields), pass proofOfPurchaseData inside the entry object. This data is stored separately in io.tt and linked to the participant record — it is not forwarded to native CRM integrations (Salesforce or Klaviyo), but is included in custom webhook payloads and is available in the io.tt dashboard for review.
const history = await sdk.registerLoyaltyUser(
EXPERIENCE_ID,
{
itemSlug,
email,
proofOfPurchaseData: [ // optional: ProofOfPurchaseItem[]
{
type: 'image', // required — free-form string identifying the field type
label: 'Receipt', // required — human-readable label
value: imageBlob, // required — Blob for images
itemId: 'abc', // optional — links to a specific item
},
{
type: 'text',
label: 'Barcode',
value: '123456789', // string for text values
},
],
},
);For image uploads alongside proof-of-purchase data, pass the files as the third images argument as shown above.
4. Claim the Stamp (claimLoyaltyItem)
Once the user is registered and the code is confirmed eligible, claim the stamp. The server validates all configured rules — rescan limits, cooldown periods, and item claim limits — before accepting the claim.
await sdk.claimLoyaltyItem(EXPERIENCE_ID, itemSlug, email);5. Fetch Stamp History (getStampHistory)
After a successful claim, fetch the updated stamp card. This gives you the full list of stamps earned so far, which you can use to render the card progression UI and determine whether the user has reached the reward threshold.
const history = await sdk.getStampHistory(EXPERIENCE_ID, email);Example response:
{
"stamps": [
{ "createdAt": "2025-01-10T10:00:00Z" },
{ "createdAt": "2025-01-12T12:00:00Z" }
]
}6. Reward Unlock
Once the required stamp count is reached, show the reward screen. The required stamp count is set in the io.tt dashboard when the experience is configured — read it from your experience configuration rather than hardcoding it.
const requiredStamps = 6; // configured in the dashboard
if (history.stamps.length >= requiredStamps) {
renderRewardScreen();
}Summary Table
| API Method | Purpose |
|---|---|
| getItemClaimStatus | Determines whether a scan can earn a stamp |
| registerLoyaltyUser | Registers a new user and returns initial stamp history |
| claimLoyaltyItem | Claims a stamp for the user against a product code |
| getStampHistory | Fetches the full list of the user's stamps |