Skip to main content

Extending Perk Claims with Custom Logic

COMING SOON - CONCEPTUAL FEATURES

⚠️ The extension features described in this guide are not yet built. They represent potential features we're considering building based on market research and customer feedback.

Current Status:

  • ✅ Core perk claiming works (LTZ burn + NFT mint + gas relaying)
  • 🔄 Extension features are in validation/planning phase
  • 📋 We're gathering requirements from brands

Interested in these features? Contact [email protected] to:

  • Share your extension needs
  • Influence our roadmap
  • Get early access when available

This guide serves as a design document and roadmap, showing what's possible with managed extensions.


Two Approaches to Custom Experiences

When a brand wants to add custom logic to perk claims (e.g., collect Discord ID, mint external NFT, assign roles), there are two architectural approaches:

Approach A: Brand Builds Custom Backend (DIY)

Brands build their own backend systems to handle custom logic.

Status: ✅ Possible today (you have the tools) Complexity: High (requires backend engineers) Cost: Development time + infrastructure Support: Limited (documentation only)

Approach B: Managed Extensions (Full-Service)

Loyalteez provides pre-built extension features that brands configure.

Status: 🔄 Coming Soon (validation phase) Complexity: Low (configuration only) Cost: Monthly subscription per extension Support: Full platform support


DIY Approach (Available Today)

If you have backend engineering resources, you can build custom perk experiences today:

What You Can Do Now:

Philosophy: Infrastructure Layer

Loyalteez operates as an infrastructure layer, not a full-service suite. This means:

What Loyalteez Handles:

  • ✅ LTZ token distribution and burns
  • ✅ Perk NFT minting and claiming
  • ✅ Gas relaying (gasless transactions)
  • ✅ Event tracking and automation
  • ✅ Core claiming mechanics

What Brands Handle:

  • 🎨 Custom data collection during claims
  • 🎨 Additional validation logic
  • 🎨 Post-claim actions and integrations
  • 🎨 External NFT minting
  • 🎨 Gated access and permissions
  • 🎨 Custom user experiences

This separation keeps Loyalteez flexible, scalable, and composable while giving brands full control over their unique experiences.


Extension Points

Brands can extend the claiming flow at three key points:

1. Pre-Claim: Custom Validation & Data Collection

2. Claim: Loyalteez Infrastructure

3. Post-Claim: Custom Actions & Integrations


Pre-Claim Extension Patterns

Pattern 1: Collect Additional User Data

Use Case: Collect Discord ID, referral code, or other data when user claims a perk.

Implementation:

import { usePrivy } from '@privy-io/react-auth';
import { useState } from 'react';

function CustomClaimFlow({ collectionId, priceInPoints }) {
const [customData, setCustomData] = useState({
discordId: '',
referralCode: '',
consentGiven: false
});

async function handleClaimWithData() {
// 1. Validate custom data
if (!customData.discordId) {
alert('Please enter your Discord ID');
return;
}

if (!customData.consentGiven) {
alert('Please accept terms and conditions');
return;
}

// 2. Store custom data in YOUR database
const claimIntent = await fetch('https://yourbrand.com/api/claim-intent', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
userAddress: user.wallet.address,
collectionId,
discordId: customData.discordId,
referralCode: customData.referralCode,
timestamp: Date.now()
})
});

const { intentId } = await claimIntent.json();

// 3. Execute claim via Loyalteez
try {
const txHash = await claimPerk(collectionId, priceInPoints);

// 4. Link claim to your custom data
await fetch('https://yourbrand.com/api/finalize-claim', {
method: 'POST',
body: JSON.stringify({
intentId,
txHash,
success: true
})
});

alert('Perk claimed successfully! 🎉');
} catch (error) {
// Mark intent as failed
await fetch('https://yourbrand.com/api/finalize-claim', {
method: 'POST',
body: JSON.stringify({
intentId,
success: false,
error: error.message
})
});
throw error;
}
}

return (
<div className="custom-claim-form">
<h3>Claim Perk</h3>

<label>
Discord ID:
<input
type="text"
value={customData.discordId}
onChange={(e) => setCustomData({...customData, discordId: e.target.value})}
placeholder="YourUsername#1234"
/>
</label>

<label>
Referral Code (optional):
<input
type="text"
value={customData.referralCode}
onChange={(e) => setCustomData({...customData, referralCode: e.target.value})}
placeholder="FRIEND2024"
/>
</label>

<label>
<input
type="checkbox"
checked={customData.consentGiven}
onChange={(e) => setCustomData({...customData, consentGiven: e.target.checked})}
/>
I agree to the terms and conditions
</label>

<button onClick={handleClaimWithData}>
Claim for {priceInPoints.toString()} LTZ
</button>
</div>
);
}

Backend (Node.js Example):

// Store claim intent
app.post('/api/claim-intent', async (req, res) => {
const { userAddress, collectionId, discordId, referralCode, timestamp } = req.body;

// Validate inputs
if (!userAddress || !collectionId || !discordId) {
return res.status(400).json({ error: 'Missing required fields' });
}

// Store in database
const intent = await db.claimIntents.create({
id: generateId(),
userAddress: userAddress.toLowerCase(),
collectionId: collectionId.toString(),
discordId,
referralCode: referralCode || null,
timestamp: new Date(timestamp),
status: 'pending'
});

res.json({ intentId: intent.id });
});

// Finalize claim after blockchain transaction
app.post('/api/finalize-claim', async (req, res) => {
const { intentId, txHash, success, error } = req.body;

const intent = await db.claimIntents.findById(intentId);
if (!intent) {
return res.status(404).json({ error: 'Intent not found' });
}

// Update intent status
await db.claimIntents.update(intentId, {
status: success ? 'completed' : 'failed',
txHash: txHash || null,
error: error || null,
completedAt: new Date()
});

// If successful, trigger post-claim actions
if (success && txHash) {
await triggerPostClaimActions(intent, txHash);
}

res.json({ success: true });
});

Pattern 2: CAPTCHA Verification

Use Case: Prevent bot abuse by requiring CAPTCHA before claim.

Implementation (with Cloudflare Turnstile):

import { useState } from 'react';
import { Turnstile } from '@marsidev/react-turnstile';

function CaptchaProtectedClaim({ collectionId, priceInPoints }) {
const [captchaToken, setCaptchaToken] = useState<string | null>(null);
const [isVerified, setIsVerified] = useState(false);

async function verifyCaptcha(token: string) {
// Verify CAPTCHA on your backend
const response = await fetch('https://yourbrand.com/api/verify-captcha', {
method: 'POST',
body: JSON.stringify({ token })
});

const { success } = await response.json();
setIsVerified(success);
return success;
}

async function handleClaim() {
if (!isVerified) {
alert('Please complete the CAPTCHA verification');
return;
}

// Proceed with claim
const txHash = await claimPerk(collectionId, priceInPoints);
alert('Perk claimed! 🎉');
}

return (
<div className="captcha-claim">
<h3>Claim Perk</h3>

<Turnstile
siteKey={process.env.VITE_TURNSTILE_SITE_KEY}
onSuccess={(token) => {
setCaptchaToken(token);
verifyCaptcha(token);
}}
/>

<button
onClick={handleClaim}
disabled={!isVerified}
>
Claim for {priceInPoints.toString()} LTZ
</button>
</div>
);
}

Backend Verification:

app.post('/api/verify-captcha', async (req, res) => {
const { token } = req.body;

// Verify with Cloudflare Turnstile
const verification = await fetch('https://challenges.cloudflare.com/turnstile/v0/siteverify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
secret: process.env.TURNSTILE_SECRET_KEY,
response: token
})
});

const result = await verification.json();

if (result.success) {
// Store verified token (prevent reuse)
await db.verifiedCaptchas.create({
token,
timestamp: new Date(),
used: false
});

res.json({ success: true });
} else {
res.status(400).json({ success: false, error: 'CAPTCHA verification failed' });
}
});

Pattern 3: Eligibility Gating

Use Case: Only allow certain users to claim based on criteria (whitelist, NFT ownership, etc.).

async function checkEligibility(userAddress: string, collectionId: bigint): Promise<boolean> {
// Check if user is whitelisted
const whitelisted = await fetch(
`https://yourbrand.com/api/whitelist/${collectionId}/${userAddress}`
).then(r => r.json());

if (!whitelisted.eligible) {
throw new Error('You are not eligible to claim this perk');
}

// Check if user owns required NFT
const hasRequiredNFT = await checkNFTOwnership(
userAddress,
'0xYourNFTContract',
1 // Minimum balance
);

if (!hasRequiredNFT) {
throw new Error('You must own a membership NFT to claim this perk');
}

return true;
}

async function claimWithEligibilityCheck(collectionId: bigint, priceInPoints: bigint) {
// Verify eligibility BEFORE claiming
await checkEligibility(user.wallet.address, collectionId);

// Proceed with claim
return await claimPerk(collectionId, priceInPoints);
}

Post-Claim Extension Patterns

Pattern 4: Trigger External NFT Mint

Use Case: When user claims a perk, mint a random NFT from your external collection.

Architecture:

User Claims Perk (Loyalteez)

PerkClaimed Event Emitted

Your Backend Listens

Mint Random NFT (Your Contract)

Notify User

Implementation:

// Backend: Listen for PerkClaimed events
import { ethers } from 'ethers';

const provider = new ethers.JsonRpcProvider('https://rpc.soneium.org');
const perkNFT = new ethers.Contract(
'0x6ae30d6Dcf3e75456B6582b057f1Bf98A90F2CA0',
[
'event PerkClaimed(uint256 indexed collectionId, address indexed claimer, uint256 indexed tokenId)'
],
provider
);

// Listen for claims on your collections
perkNFT.on('PerkClaimed', async (collectionId, claimer, tokenId, event) => {
try {
// Check if this is YOUR brand's collection
const collection = await perkNFT.collections(collectionId);

if (collection.creator.toLowerCase() === YOUR_BRAND_ADDRESS.toLowerCase()) {
console.log('🎉 Perk claimed on your collection!', {
collectionId: collectionId.toString(),
claimer,
tokenId: tokenId.toString()
});

// Trigger custom NFT mint
await mintRandomNFT(claimer, collectionId, tokenId, event.transactionHash);
}
} catch (error) {
console.error('Error processing perk claim:', error);
// Retry or queue for later
}
});

async function mintRandomNFT(
recipient: string,
loyalteezCollectionId: bigint,
loyalteezTokenId: bigint,
loyalteezTxHash: string
) {
// 1. Select random NFT from your collection
const randomNFT = await selectRandomFromPool(loyalteezCollectionId);

// 2. Mint via your contract
const yourNFTContract = new ethers.Contract(
'0xYourNFTContract',
['function mint(address to, string uri) returns (uint256)'],
signer // Your backend wallet
);

const mintTx = await yourNFTContract.mint(
recipient,
randomNFT.tokenURI
);

await mintTx.wait();

// 3. Record the association
await db.customMints.create({
loyalteezCollectionId: loyalteezCollectionId.toString(),
loyalteezTokenId: loyalteezTokenId.toString(),
loyalteezTxHash,
customNFTContract: '0xYourNFTContract',
customTokenId: mintTx.tokenId,
recipient,
randomNFTMetadata: randomNFT,
timestamp: new Date()
});

// 4. Notify user
await notifyUser(recipient, {
message: `You've received a bonus NFT! Check your wallet.`,
nftContract: '0xYourNFTContract',
tokenId: mintTx.tokenId,
explorerUrl: `https://soneium.blockscout.com/token/${yourNFTContract.address}/instance/${mintTx.tokenId}`
});

console.log('✅ Minted custom NFT:', mintTx.tokenId);
}

async function selectRandomFromPool(collectionId: bigint) {
// Get available NFTs for this collection
const pool = await db.nftPool.find({
collectionId: collectionId.toString(),
claimed: false
});

if (pool.length === 0) {
throw new Error('No NFTs available in pool');
}

// Select random NFT
const randomIndex = Math.floor(Math.random() * pool.length);
const selectedNFT = pool[randomIndex];

// Mark as claimed
await db.nftPool.update(selectedNFT.id, { claimed: true });

return selectedNFT;
}

Pattern 5: Discord Role Assignment

Use Case: Grant Discord role when user claims a perk.

import { REST } from '@discordjs/rest';
import { Routes } from 'discord-api-types/v10';

const discord = new REST({ version: '10' }).setToken(process.env.DISCORD_BOT_TOKEN);

async function assignDiscordRole(
userAddress: string,
collectionId: bigint,
tokenId: bigint
) {
// 1. Get user's Discord ID from your database
const user = await db.users.findOne({ walletAddress: userAddress.toLowerCase() });

if (!user || !user.discordId) {
console.log('User has not linked Discord account');
return;
}

// 2. Determine which role to assign based on collection
const roleMapping = {
'0': process.env.DISCORD_ROLE_VIP, // Collection 0 → VIP role
'1': process.env.DISCORD_ROLE_PREMIUM, // Collection 1 → Premium role
'2': process.env.DISCORD_ROLE_EXCLUSIVE // Collection 2 → Exclusive role
};

const roleId = roleMapping[collectionId.toString()];

if (!roleId) {
console.log('No Discord role mapped for collection', collectionId);
return;
}

// 3. Assign role via Discord API
try {
await discord.put(
Routes.guildMemberRole(process.env.DISCORD_GUILD_ID, user.discordId, roleId)
);

console.log(`✅ Assigned Discord role to ${user.discordId}`);

// 4. Send DM notification
const dmChannel = await discord.post(Routes.userChannels(), {
body: { recipient_id: user.discordId }
});

await discord.post(Routes.channelMessages(dmChannel.id), {
body: {
content: `🎉 You've unlocked an exclusive role! Check the server for your new permissions.`
}
});
} catch (error) {
console.error('Failed to assign Discord role:', error);
}
}

// Call from PerkClaimed event handler
perkNFT.on('PerkClaimed', async (collectionId, claimer, tokenId) => {
// ... verify it's your collection ...
await assignDiscordRole(claimer, collectionId, tokenId);
});

Pattern 6: Webhook Notifications

Use Case: Notify external systems when a perk is claimed.

async function sendWebhooks(
claimer: string,
collectionId: bigint,
tokenId: bigint,
txHash: string
) {
const webhooks = await db.webhooks.findAll({
collectionId: collectionId.toString(),
enabled: true
});

for (const webhook of webhooks) {
try {
await fetch(webhook.url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Webhook-Secret': webhook.secret
},
body: JSON.stringify({
event: 'perk.claimed',
data: {
collectionId: collectionId.toString(),
claimer,
tokenId: tokenId.toString(),
transactionHash: txHash,
timestamp: new Date().toISOString()
}
})
});

console.log(`✅ Webhook sent to ${webhook.url}`);
} catch (error) {
console.error(`Failed to send webhook to ${webhook.url}:`, error);

// Queue for retry
await db.webhookQueue.create({
webhookId: webhook.id,
payload: { collectionId, claimer, tokenId, txHash },
attempts: 0,
maxAttempts: 3
});
}
}
}

Pattern 7: Email/SMS Notifications

Use Case: Send confirmation email or SMS when perk is claimed.

import { Resend } from 'resend';

const resend = new Resend(process.env.RESEND_API_KEY);

async function sendClaimNotification(
claimer: string,
collectionId: bigint,
tokenId: bigint
) {
// Get user's email
const user = await db.users.findOne({ walletAddress: claimer.toLowerCase() });

if (!user || !user.email) {
console.log('No email address for user');
return;
}

// Get collection details
const collection = await perkNFT.collections(collectionId);
const metadata = await fetchCollectionMetadata(collection.metadataHash);

// Send email
await resend.emails.send({
from: '[email protected]',
to: user.email,
subject: `You've claimed ${metadata.name}! 🎉`,
html: `
<h1>Perk Claimed Successfully!</h1>
<p>Congratulations! You've successfully claimed <strong>${metadata.name}</strong>.</p>
<p><strong>Token ID:</strong> ${tokenId}</p>
<p><strong>View on Explorer:</strong> <a href="https://soneium.blockscout.com/token/0x6ae30d6Dcf3e75456B6582b057f1Bf98A90F2CA0/instance/${tokenId}">View NFT</a></p>
<p>Your perk is now available in your wallet!</p>
`
});

console.log(`✅ Email sent to ${user.email}`);
}

Complete Example: Multi-Step Custom Flow

Scenario: Brand wants to:

  1. Collect Discord ID before claim
  2. Verify user via CAPTCHA
  3. Claim perk via Loyalteez
  4. Mint bonus NFT from external collection
  5. Assign Discord role
  6. Send email confirmation

Frontend:

import { useState } from 'react';
import { Turnstile } from '@marsidev/react-turnstile';
import { usePrivy } from '@privy-io/react-auth';

function CompleteCustomClaimFlow({ collection }) {
const { user } = usePrivy();
const [step, setStep] = useState(1);
const [formData, setFormData] = useState({
discordId: '',
captchaToken: null,
consentGiven: false
});
const [claiming, setClaiming] = useState(false);
const [result, setResult] = useState(null);

async function handleSubmit() {
setClaiming(true);

try {
// Step 1: Store claim intent with custom data
const intent = await fetch('https://yourbrand.com/api/claim-intent', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
userAddress: user.wallet.address,
collectionId: collection.id,
discordId: formData.discordId,
captchaToken: formData.captchaToken,
timestamp: Date.now()
})
}).then(r => r.json());

// Step 2: Execute claim via Loyalteez
const txHash = await claimPerk(collection.id, collection.priceInPoints);

// Step 3: Finalize claim (triggers backend post-claim actions)
const finalization = await fetch('https://yourbrand.com/api/finalize-claim', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
intentId: intent.intentId,
txHash,
success: true
})
}).then(r => r.json());

setResult({
success: true,
txHash,
bonusNFT: finalization.bonusNFT,
discordRole: finalization.discordRole
});

setStep(4); // Success screen

} catch (error) {
setResult({
success: false,
error: error.message
});
setStep(4); // Error screen
} finally {
setClaiming(false);
}
}

return (
<div className="custom-claim-flow">
{step === 1 && (
<div>
<h3>Step 1: Link Your Discord</h3>
<input
type="text"
placeholder="YourUsername#1234"
value={formData.discordId}
onChange={(e) => setFormData({...formData, discordId: e.target.value})}
/>
<button
onClick={() => setStep(2)}
disabled={!formData.discordId}
>
Next
</button>
</div>
)}

{step === 2 && (
<div>
<h3>Step 2: Verify You're Human</h3>
<Turnstile
siteKey={process.env.VITE_TURNSTILE_SITE_KEY}
onSuccess={(token) => {
setFormData({...formData, captchaToken: token});
setStep(3);
}}
/>
</div>
)}

{step === 3 && (
<div>
<h3>Step 3: Confirm Claim</h3>
<div className="summary">
<p><strong>Perk:</strong> {collection.name}</p>
<p><strong>Cost:</strong> {collection.priceInPoints.toString()} LTZ</p>
<p><strong>Discord:</strong> {formData.discordId}</p>
</div>

<label>
<input
type="checkbox"
checked={formData.consentGiven}
onChange={(e) => setFormData({...formData, consentGiven: e.target.checked})}
/>
I agree to the terms and conditions
</label>

<button
onClick={handleSubmit}
disabled={!formData.consentGiven || claiming}
>
{claiming ? 'Claiming...' : 'Claim Perk'}
</button>
</div>
)}

{step === 4 && result && (
<div>
{result.success ? (
<div className="success">
<h3>🎉 Perk Claimed Successfully!</h3>
<p><strong>Transaction:</strong> <a href={`https://soneium.blockscout.com/tx/${result.txHash}`} target="_blank">{result.txHash.slice(0, 10)}...</a></p>
{result.bonusNFT && (
<p><strong>Bonus NFT:</strong> You received a bonus NFT! Check your wallet.</p>
)}
{result.discordRole && (
<p><strong>Discord Role:</strong> Check your server for your new exclusive role!</p>
)}
</div>
) : (
<div className="error">
<h3>❌ Claim Failed</h3>
<p>{result.error}</p>
<button onClick={() => setStep(1)}>Try Again</button>
</div>
)}
</div>
)}
</div>
);
}

Backend:

// Complete backend flow
app.post('/api/finalize-claim', async (req, res) => {
const { intentId, txHash, success } = req.body;

const intent = await db.claimIntents.findById(intentId);

if (!intent) {
return res.status(404).json({ error: 'Intent not found' });
}

// Update intent
await db.claimIntents.update(intentId, {
status: success ? 'completed' : 'failed',
txHash: txHash || null,
completedAt: new Date()
});

if (!success) {
return res.json({ success: false });
}

// Trigger all post-claim actions
const [bonusNFT, discordRole, emailSent] = await Promise.all([
// 1. Mint bonus NFT
mintRandomNFT(intent.userAddress, intent.collectionId, txHash),

// 2. Assign Discord role
assignDiscordRole(intent.userAddress, intent.collectionId),

// 3. Send email notification
sendClaimNotification(intent.userAddress, intent.collectionId, txHash)
]);

res.json({
success: true,
bonusNFT,
discordRole,
emailSent
});
});

Best Practices

✅ Do's

  • Store custom data in YOUR database - Don't rely on blockchain for non-essential data
  • Validate inputs - Always validate custom data before storing
  • Handle failures gracefully - Retry post-claim actions if they fail
  • Use idempotency - Ensure post-claim actions can be safely retried
  • Log everything - Track claim intents, transactions, and post-claim actions
  • Test thoroughly - Test all custom flows before production
  • Provide clear UX - Show progress, loading states, and error messages

❌ Don'ts

  • ❌ Don't block the claim on external APIs - If Discord/email fails, still complete the claim
  • ❌ Don't store sensitive data on-chain - Use your database for custom data
  • ❌ Don't trust client-side validation alone - Always verify on backend
  • ❌ Don't retry indefinitely - Set max retry limits and queue for manual review
  • ❌ Don't expose API keys client-side - All external integrations should be server-side

Security Considerations

Validate Blockchain Events

Always verify events are legitimate:

async function verifyClaimEvent(txHash: string, expectedClaimer: string) {
// Get transaction receipt
const receipt = await provider.getTransactionReceipt(txHash);

if (!receipt) {
throw new Error('Transaction not found');
}

// Parse PerkClaimed event
const perkClaimedEvent = receipt.logs
.filter(log => log.address === PERK_NFT_ADDRESS)
.map(log => perkNFT.interface.parseLog(log))
.find(parsed => parsed.name === 'PerkClaimed');

if (!perkClaimedEvent) {
throw new Error('PerkClaimed event not found in transaction');
}

// Verify claimer matches
if (perkClaimedEvent.args.claimer.toLowerCase() !== expectedClaimer.toLowerCase()) {
throw new Error('Claimer mismatch');
}

return true;
}

Prevent Replay Attacks

Track processed transactions:

async function processClaimOnce(txHash: string) {
// Check if already processed
const existing = await db.processedClaims.findOne({ txHash });

if (existing) {
console.log('Transaction already processed:', txHash);
return existing;
}

// Process claim
const result = await processCustomLogic(txHash);

// Mark as processed
await db.processedClaims.create({
txHash,
processedAt: new Date(),
result
});

return result;
}


Managed Extensions Approach (Future Roadmap)

UNDER CONSIDERATION

These are conceptual features we're considering building. They would allow brands to configure pre-built extensions rather than building custom backends.

Share your feedback: [email protected]

Vision: Configuration Over Code

Instead of brands building everything themselves, we're considering offering managed extensions that brands simply configure:

Potential Extension: Discord Integration

Instead of building:

  • Backend event listeners
  • Discord API integration
  • Database for Discord IDs
  • Error handling and retries

Brands would:

  1. Toggle "Discord Integration" in Partner Portal
  2. Connect Discord bot
  3. Map collections to roles
  4. Done ✅

Pricing (Conceptual): $49/month


Potential Extension: Custom NFT Minting

Instead of building:

  • Event listeners
  • NFT selection logic
  • Minting orchestration
  • Transaction monitoring

Brands would:

  1. Toggle "Custom NFT Minting"
  2. Upload NFT metadata pool
  3. Configure rarity weights
  4. Done ✅

Pricing (Conceptual): $99/month


Potential Extension: Pre-Claim Validation

Instead of building:

  • CAPTCHA integration
  • Whitelist checking
  • NFT gating verification
  • Custom form fields

Brands would:

  1. Toggle validation features
  2. Upload whitelist (if needed)
  3. Configure requirements
  4. Done ✅

Pricing (Conceptual): $29/month


Why We're Considering This

Benefits for Brands:

  • ✅ No engineering resources needed
  • ✅ Faster time to market
  • ✅ We handle maintenance and updates
  • ✅ Built-in error handling and monitoring
  • ✅ Support included
  • Combined with user behavior analytics (solve the open-loop data problem)

Benefits for Loyalteez:

  • ✅ Stronger competitive moat
  • ✅ SaaS revenue model
  • ✅ Better for non-technical brands
  • ✅ Control full experience

Strategic Direction: We're considering a dual subscription model:

  1. Data Analytics Subscription ($49-149/mo)

    • Solves: Open-loop loses user behavior tracking
    • Provides: Full analytics dashboard with user insights
  2. Managed Extensions Subscription ($99-299/mo)

    • Solves: Technical complexity of custom integrations
    • Provides: Pre-built extensions (Discord, Email, NFT minting, etc.)

Why Together? Open-loop benefits (interoperability) + closed-loop insights (analytics) + managed automation (extensions) = Best of both worlds.

Current Status: We're in the validation phase - gathering feedback from brands about:

  • Would you value user behavior analytics?
  • Which extensions are most valuable?
  • What would you pay for each tier?
  • What other features do you need?

Want to influence our roadmap? Contact us



Support

Building custom extensions yourself?

Interested in managed extensions?

  • 📧 Share your needs: [email protected]
  • 📋 Influence our roadmap
  • 🎯 Get early access

Note: Currently, brands with engineering resources can build custom integrations using the DIY patterns in this guide. Managed extensions are under consideration based on market demand.