Skip to main content

OAuth Pregeneration - Reward Users Before They Log In

๐ŸŽ‰ Status: Production Ready | โฑ๏ธ Setup Time: 20 minutes | ๐Ÿ”ง Skill Level: โญโญ Intermediate

What is this? A fully operational API that lets you reward users based on their social media identities (Discord, Twitter, GitHub, etc.) before they ever log into your app.

Use this if you're: Building games, bots, or apps where you know your users' social IDs but they haven't logged into Loyalteez yet.

What you need: Just your Loyalteez Brand ID - no Privy credentials required!


The Problem This Solvesโ€‹

Traditional Flow (Limitation)โ€‹

1. User logs into your app with Discord
2. Privy creates wallet for them
3. THEN you can reward them with LTZ

Issue: You can't reward users who haven't logged in yet.

Pregeneration Flow (Solution)โ€‹

1. User earns reward in your Discord server (never logged in)
2. You pregenerate a Privy account linked to their Discord
3. LTZ is deposited to their pregenerated wallet
4. When they eventually log in with Discord โ†’ they see their rewards! ๐ŸŽ‰

Benefit: Reward users immediately, they discover their rewards when they log in.


How It Worksโ€‹

Plain English Explanationโ€‹

Think of it like:

  • You're sending a gift card to someone's email
  • Even if they don't have an account yet
  • When they create an account with that email โ†’ the gift is waiting!

Technical explanation:

  • Privy's API lets you create user accounts linked to OAuth identities
  • Each OAuth provider (Discord, Twitter, etc.) has unique user IDs
  • You can create a wallet for a user using their OAuth ID
  • When they log in with that OAuth provider โ†’ they get the same wallet
  • It's deterministic: Same OAuth ID = Same wallet (always)

Use Casesโ€‹

1. Discord Game Botโ€‹

Scenario: You have a Discord bot game where users earn points.

User plays game in Discord โ†’ Earns 100 LTZ
User has never used Loyalteez before
Bot pregenerates wallet linked to Discord ID
User sees "You earned 100 LTZ! Log in at loyalteez.app to claim"
User logs in with Discord โ†’ 100 LTZ is already there!

2. Twitter/X Engagement Campaignโ€‹

Scenario: Reward people who tweet about your product.

User tweets with #YourHashtag
Your bot detects the tweet
Pregenerate wallet linked to Twitter ID
Award 50 LTZ to their wallet
DM them: "You earned 50 LTZ! Log in to see your rewards"

3. GitHub Contributionsโ€‹

Scenario: Reward open source contributors.

User submits a PR to your repo
GitHub webhook triggers your server
Pregenerate wallet linked to GitHub ID
Award 200 LTZ to their wallet
Comment on PR: "Thanks! 200 LTZ credited to your account"

4. Multi-Platform Onboardingโ€‹

Scenario: User interacts on multiple platforms before signing up.

Day 1: User joins Discord server โ†’ Pregenerate + award 10 LTZ
Day 2: User retweets your post โ†’ Award 5 LTZ to same user
Day 3: User finally logs in โ†’ Sees 15 LTZ total waiting!

Implementation Guideโ€‹

Prerequisitesโ€‹

You'll need:

  • Loyalteez Brand ID (get this from your dashboard)
  • Backend server (Node.js, Python, or any language with HTTP support)
  • User's OAuth provider and their ID (from their real OAuth account)

Important: โš ๏ธ NO Privy credentials needed! You call Loyalteez's API and we handle all Privy integration server-side for security!


Step 1: Understanding the Architectureโ€‹

Why this architecture?

โŒ Wrong approach: Use your own Privy account

  • Wallets created in YOUR Privy instance
  • Not connected to Loyalteez system
  • Can't receive LTZ rewards

โœ… Correct approach: Call Loyalteez API

  • We use OUR Privy credentials server-side
  • Wallets created in Loyalteez's Privy instance
  • Fully integrated with LTZ rewards
  • User sees everything when they log in

No SDK installation needed - Just make HTTP requests to our API!


Step 2: Server-Side Pregeneration Codeโ€‹

Node.js / TypeScriptโ€‹

import axios from 'axios';

// =========================================
// FUNCTION: Pregenerate User + Reward LTZ
// =========================================
async function rewardUserByOAuth(
provider: 'discord' | 'twitter' | 'github' | 'google' | 'telegram',
userId: string, // Their ID on that platform
username: string, // Their username (for display)
ltzAmount: number // How much LTZ to award
) {
try {
// ==========================================
// STEP 1: Pregenerate Wallet via Loyalteez API
// ==========================================
// Calls Loyalteez's pregeneration endpoint
// We handle Privy integration server-side

const pregenerationResponse = await axios.post(
'https://register.loyalteez.app/loyalteez-api/pregenerate-user',
{
brand_id: process.env.VITE_LOYALTEEZ_BRAND_ID,
oauth_provider: provider,
oauth_user_id: userId,
oauth_username: username,
metadata: {
source: 'automatic_reward',
timestamp: Date.now()
}
}
);

const { wallet_address } = pregenerationResponse.data;

console.log(`โœ… Pregenerated wallet for ${username}`);
console.log(`Wallet address: ${wallet_address}`);

// ==========================================
// STEP 2: Award LTZ to Their Wallet
// ==========================================
// Use Loyalteez event API to credit LTZ

await axios.post('https://api.loyalteez.app/loyalteez-api/manual-event', {
event: 'oauth_pregeneration_reward',
email: `${provider}_${userId}@pregenerated.loyalteez.app`, // Synthetic email
metadata: {
brand_id: process.env.VITE_LOYALTEEZ_BRAND_ID,
oauth_provider: provider,
oauth_user_id: userId,
oauth_username: username,
wallet_address: wallet_address,
reward_amount: ltzAmount
}
});

console.log(`โœ… Awarded ${ltzAmount} LTZ to ${username}`);

return {
success: true,
wallet: wallet_address,
message: `${username} earned ${ltzAmount} LTZ! Log in with ${provider} to claim.`
};

} catch (error) {
console.error('โŒ Pregeneration failed:', error);
throw error;
}
}

// ==========================================
// EXAMPLE USAGE: Discord Bot
// ==========================================
async function handleDiscordGameWin(discordUserId: string, discordUsername: string) {
const result = await rewardUserByOAuth(
'discord',
discordUserId, // e.g., "123456789012345678"
discordUsername, // e.g., "gamer123"
100 // Award 100 LTZ
);

// Send them a DM or message
return `๐ŸŽ‰ Congrats ${discordUsername}! ${result.message}`;
}

// ==========================================
// EXAMPLE USAGE: Twitter Engagement
// ==========================================
async function handleTwitterMention(twitterUserId: string, twitterHandle: string) {
const result = await rewardUserByOAuth(
'twitter',
twitterUserId, // e.g., "987654321"
twitterHandle, // e.g., "@cryptofan"
50 // Award 50 LTZ
);

// Reply to their tweet or DM them
return `Thanks ${twitterHandle}! ${result.message}`;
}

// ==========================================
// EXAMPLE USAGE: GitHub Contribution
// ==========================================
async function handleGitHubPR(githubUserId: string, githubUsername: string) {
const result = await rewardUserByOAuth(
'github',
githubUserId, // e.g., "45678901"
githubUsername, // e.g., "dev_contributor"
200 // Award 200 LTZ for PR
);

// Comment on PR
return `๐ŸŽ‰ ${result.message}`;
}

Pythonโ€‹

import requests
import os

def reward_user_by_oauth(provider, user_id, username, ltz_amount):
"""
Pregenerate user wallet and reward them with LTZ

Args:
provider: 'discord', 'twitter', 'github', 'google', or 'telegram'
user_id: User's unique ID on that platform
username: User's display name
ltz_amount: Amount of LTZ to award
"""
try:
# STEP 1: Pregenerate wallet via Loyalteez API
pregeneration_response = requests.post(
'https://register.loyalteez.app/loyalteez-api/pregenerate-user',
json={
'brand_id': os.getenv('VITE_LOYALTEEZ_BRAND_ID'),
'oauth_provider': provider,
'oauth_user_id': user_id,
'oauth_username': username,
'metadata': {
'source': 'automatic_reward',
'timestamp': int(time.time() * 1000)
}
}
)

pregeneration_response.raise_for_status()
wallet_data = pregeneration_response.json()
wallet_address = wallet_data['wallet_address']

print(f"โœ… Pregenerated wallet for {username}")
print(f"Wallet address: {wallet_address}")

# STEP 2: Award LTZ via event API
reward_response = requests.post(
'https://api.loyalteez.app/loyalteez-api/manual-event',
json={
'event': 'oauth_pregeneration_reward',
'email': f"{provider}_{user_id}@pregenerated.loyalteez.app",
'metadata': {
'brand_id': os.getenv('VITE_LOYALTEEZ_BRAND_ID'),
'oauth_provider': provider,
'oauth_user_id': user_id,
'oauth_username': username,
'wallet_address': wallet_address,
'reward_amount': ltz_amount
}
}
)

reward_response.raise_for_status()
print(f"โœ… Awarded {ltz_amount} LTZ to {username}")

return {
'success': True,
'wallet': wallet_address,
'message': f"{username} earned {ltz_amount} LTZ! Log in with {provider} to claim."
}

except Exception as e:
print(f"โŒ Pregeneration failed: {e}")
raise

# Example: Discord bot reward
def handle_discord_game_win(discord_user_id, discord_username):
return reward_user_by_oauth('discord', discord_user_id, discord_username, 100)

REST API (Any Language)โ€‹

Simple HTTP requests - works with any programming language:

# Step 1: Pregenerate wallet via Loyalteez API
curl -X POST https://register.loyalteez.app/loyalteez-api/pregenerate-user \
-H "Content-Type: application/json" \
-d '{
"brand_id": "your-brand-id",
"oauth_provider": "discord",
"oauth_user_id": "123456789012345678",
"oauth_username": "gamer123",
"metadata": {
"source": "game_bot",
"game": "space_adventure"
}
}'

# Response:
# {
# "success": true,
# "wallet_address": "0xABC123...",
# "privy_user_id": "did:privy:...",
# "oauth_provider": "discord",
# "oauth_user_id": "123456789012345678",
# "message": "Wallet pregenerated for gamer123"
# }

# Step 2: Award LTZ to the wallet
curl -X POST https://api.loyalteez.app/loyalteez-api/manual-event \
-H "Content-Type: application/json" \
-d '{
"event": "oauth_pregeneration_reward",
"email": "[email protected]",
"metadata": {
"brand_id": "your-brand-id",
"oauth_provider": "discord",
"oauth_user_id": "123456789012345678",
"oauth_username": "gamer123",
"wallet_address": "0xABC123...",
"reward_amount": 100
}
}'

That's it! Two API calls and you're done. No SDKs, no Privy credentials needed.


Step 3: Environment Variablesโ€‹

Add this to your .env:

# Loyalteez Configuration
VITE_LOYALTEEZ_BRAND_ID=your_brand_id_here

That's it! Just your Brand ID. No Privy credentials, no API keys - we handle all authentication server-side for security.

Security: โš ๏ธ Never commit .env to Git! Add to .gitignore


Step 4: Understanding the Responseโ€‹

The pregeneration endpoint returns:

{
success: true,
wallet_address: string, // Ethereum wallet address
privy_user_id: string, // Privy DID (did:privy:...)
oauth_provider: string, // Provider you used
oauth_user_id: string, // OAuth ID you provided
created_new: boolean, // true = new wallet, false = existing
message: string, // Human-readable status
next_steps: string[] // What to do next
}

The created_new field is key:

  • true = First time pregeneration, new wallet created
  • false = Wallet already existed, returned existing one

This lets you track new users vs. returning users!


Important Detailsโ€‹

1. OAuth Provider Subject IDsโ€‹

Each OAuth provider has unique user IDs:

ProviderWhere to Get User IDExample Format
Discorduser.id from Discord API"123456789012345678" (string)
Twitter/Xuser.id from Twitter API"987654321" (string)
GitHubuser.id from GitHub API"45678901" (number as string)
Googlesub claim from OAuth token"108012345678901234567"

Critical: Always use the platform's user ID, not email or username!


2. Deterministic = Same User, Same Walletโ€‹

Key concept:

// First time: Creates wallet
rewardUserByOAuth('discord', '123456', 'user1', 50);
// Wallet created: 0xABC...

// Later: Same Discord ID = Same wallet
rewardUserByOAuth('discord', '123456', 'user1', 25);
// Same wallet: 0xABC... (gets +25 LTZ)

// User logs in with Discord
// Privy gives them: 0xABC... (has 75 LTZ total!)

This means:

  • โœ… You can reward users multiple times before they log in
  • โœ… All rewards accumulate in the same wallet
  • โœ… When they log in, everything is there!

3. Cross-Platform Considerationsโ€‹

Q: What if a user has both Discord and Twitter but you reward both?

A: They'll have separate wallets for each OAuth provider (unless Privy links them).

// Reward Discord ID
rewardUserByOAuth('discord', '123', 'user', 50);
// Creates wallet A

// Reward Twitter ID (same person!)
rewardUserByOAuth('twitter', '456', 'user', 50);
// Creates wallet B (different!)

// Solution: Pick ONE primary OAuth provider per user

Best practice:

  • Choose one primary OAuth provider (e.g., Discord)
  • Store user's primary provider in your database
  • Always use that provider for rewards

4. When Wallet Is Already Createdโ€‹

Q: What if you try to pregenerate a user who already logged in?

A: Privy returns the existing wallet, no problem!

// User already logged in โ†’ wallet exists
const result = await privy.importUser({
linkedAccounts: [{ type: 'discord', subject: '123' }],
createEmbeddedWallet: true
});

// Result: Returns existing wallet (doesn't create new one)
// You can safely call this multiple times

Complete Example: Discord Game Botโ€‹

Full Working Bot with Pregenerationโ€‹

const { Client, GatewayIntentBits } = require('discord.js');
const axios = require('axios');
require('dotenv').config();

// ==========================================
// INITIALIZE DISCORD CLIENT
// ==========================================
const discord = new Client({
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.MessageContent
]
});

// ==========================================
// GAME STATE (in production, use database)
// ==========================================
const userScores = new Map();

// ==========================================
// HELPER: Pregenerate + Reward
// ==========================================
async function rewardDiscordUser(userId, username, amount) {
try {
// Step 1: Pregenerate wallet via Loyalteez API
const pregenerationResponse = await axios.post(
'https://register.loyalteez.app/loyalteez-api/pregenerate-user',
{
brand_id: process.env.VITE_LOYALTEEZ_BRAND_ID,
oauth_provider: 'discord',
oauth_user_id: userId,
oauth_username: username,
metadata: {
source: 'discord_game_bot',
game: 'score_game'
}
}
);

const { wallet_address } = pregenerationResponse.data;

// Step 2: Award LTZ
await axios.post(
'https://api.loyalteez.app/loyalteez-api/manual-event',
{
event: 'discord_game_win',
email: `discord_${userId}@pregenerated.loyalteez.app`,
metadata: {
brand_id: process.env.VITE_LOYALTEEZ_BRAND_ID,
oauth_provider: 'discord',
oauth_user_id: userId,
oauth_username: username,
wallet_address: wallet_address,
reward_amount: amount
}
}
);

return {
success: true,
wallet: wallet_address,
amount
};
} catch (error) {
console.error('Reward failed:', error);
return { success: false, error: error.message };
}
}

// ==========================================
// GAME COMMANDS
// ==========================================
discord.on('messageCreate', async (message) => {
if (message.author.bot) return;

const userId = message.author.id;
const username = message.author.username;

// Command: !play
if (message.content === '!play') {
const score = Math.floor(Math.random() * 100);
userScores.set(userId, (userScores.get(userId) || 0) + score);

message.reply(
`๐ŸŽฎ You scored ${score} points! Total: ${userScores.get(userId)}`
);

// If score > 50, reward with LTZ
if (score > 50) {
const ltzReward = Math.floor(score / 10);
const result = await rewardDiscordUser(userId, username, ltzReward);

if (result.success) {
message.reply(
`๐ŸŽ‰ **Bonus!** You earned ${ltzReward} LTZ!\n` +
`Log in at https://loyalteez.app with Discord to see your rewards!\n` +
`Your wallet: \`${result.wallet}\``
);
}
}
}

// Command: !balance
if (message.content === '!balance') {
const points = userScores.get(userId) || 0;
message.reply(
`๐Ÿ“Š **Your Stats:**\n` +
`Game Points: ${points}\n` +
`Log in at https://loyalteez.app to see your LTZ balance!`
);
}
});

// ==========================================
// START BOT
// ==========================================
discord.login(process.env.DISCORD_BOT_TOKEN);
console.log('๐Ÿค– Discord game bot running with OAuth pregeneration!');

What This Bot Doesโ€‹

  1. User plays game: !play โ†’ random score
  2. High score (>50): Automatically pregenerates wallet and awards LTZ
  3. User checks balance: !balance โ†’ Shows stats
  4. User logs in later: Discovers all their LTZ waiting!

Best Practicesโ€‹

โœ… Do Thisโ€‹

  1. Use server-side only

    • Never expose PRIVY_APP_SECRET to browsers
    • Run pregeneration from your backend
  2. Store provider in database

    // Track which OAuth provider you used
    database.saveUser({
    internalId: 'user_123',
    oauthProvider: 'discord', // โ† Store this!
    oauthUserId: '987654321',
    firstRewardedAt: new Date()
    });
  3. Handle errors gracefully

    try {
    await rewardUserByOAuth(...);
    } catch (error) {
    // Don't break your app if Privy is down
    console.error('Reward failed:', error);
    // Queue for retry later
    }
  4. Notify users

    // Tell them about their rewards!
    sendDM(userId,
    `๐ŸŽ‰ You earned ${amount} LTZ! ` +
    `Log in at loyalteez.app with ${provider} to claim.`
    );
  5. Cache Privy responses

    // Don't create the same user multiple times
    const cached = cache.get(`privy_${userId}`);
    if (cached) return cached.wallet;

    const user = await privy.importUser(...);
    cache.set(`privy_${userId}`, user, 3600); // 1 hour

โŒ Don't Do Thisโ€‹

  1. Don't use email/username as subject

    // โŒ WRONG - Not deterministic
    subject: user.email // Emails can change!
    subject: user.username // Usernames can change!

    // โœ… CORRECT - Use platform's unique ID
    subject: user.id // Platform ID never changes
  2. Don't expose App Secret

    // โŒ WRONG - Never in frontend!
    const privy = new PrivyClient(appId, appSecret);

    // โœ… CORRECT - Backend only
    // Run pregeneration on your server
  3. Don't skip error handling

    // โŒ WRONG - Unhandled errors
    await privy.importUser(...);

    // โœ… CORRECT - Handle failures
    try {
    await privy.importUser(...);
    } catch (error) {
    logError(error);
    notifyAdmin(error);
    }
  4. Don't forget rate limits

    // โœ… Add rate limiting
    const limited = await rateLimiter.check(userId);
    if (limited) {
    return { error: 'Too many requests' };
    }

Troubleshootingโ€‹

Issue: "Invalid subject ID"โ€‹

Cause: Using wrong ID format

Fix: Check the provider's documentation for correct ID format:

// Discord: user.id (string of numbers)
subject: message.author.id // "123456789012345678"

// Twitter: user.id (string)
subject: tweet.user.id_str // "987654321"

// GitHub: user.id (number as string)
subject: String(ghUser.id) // "45678901"

Issue: "User already exists with different linked account"โ€‹

Cause: Trying to link a second OAuth provider to existing user

Fix: Each OAuth provider creates a separate user (by design):

// Pick ONE primary provider per user
// Store in your database which provider you used
const primaryProvider = await db.getUserProvider(internalUserId);
await rewardUserByOAuth(primaryProvider, ...);

Issue: "Wallet not showing rewards"โ€‹

Cause: Different OAuth provider used for pregeneration vs login

Fix: Ensure consistency:

// Database: User's primary provider
{ userId: '123', provider: 'discord', oauthId: '456' }

// Always use same provider
await rewardUserByOAuth('discord', '456', ...); // โœ…
await rewardUserByOAuth('twitter', '789', ...); // โŒ Different provider!

Issue: "Rate limited by Privy"โ€‹

Cause: Too many API calls

Fix: Implement caching and rate limiting:

// Cache user wallets
const cache = new Map();

async function getCachedWallet(provider, userId) {
const key = `${provider}_${userId}`;
if (cache.has(key)) return cache.get(key);

const user = await privy.importUser(...);
cache.set(key, user.wallet.address, 3600000); // 1 hour
return user.wallet.address;
}

Testing Guideโ€‹

Step 1: Test Pregenerationโ€‹

// Test script: test-pregeneration.js
async function test() {
console.log('๐Ÿงช Testing OAuth pregeneration...\n');

// Test 1: Create new user
console.log('Test 1: First-time pregeneration');
const result1 = await rewardUserByOAuth(
'discord',
'test_' + Date.now(), // Unique test ID
'TestUser',
10
);
console.log('โœ… Wallet created:', result1.wallet);

// Test 2: Same user again (should return same wallet)
console.log('\nTest 2: Repeat pregeneration');
const result2 = await rewardUserByOAuth(
'discord',
'test_' + Date.now(),
'TestUser',
10
);
console.log('โœ… Same wallet:', result2.wallet === result1.wallet);

console.log('\n๐ŸŽ‰ Tests passed!');
}

test().catch(console.error);

Step 2: Test Login Flowโ€‹

  1. Pregenerate a wallet for a test Discord account
  2. Log into your Loyalteez frontend with that Discord account
  3. Verify the pregenerated wallet appears
  4. Check LTZ balance shows rewards

Security Considerationsโ€‹

1. Server-Side Onlyโ€‹

// โœ… Call from your backend
fetch('https://register.loyalteez.app/loyalteez-api/pregenerate-user', {
method: 'POST',
body: JSON.stringify({ brand_id, oauth_provider, oauth_user_id })
});

// โŒ NEVER call from browser/frontend
// Users could pregenerate wallets for anyone!

2. Validate User IDsโ€‹

// โœ… Validate before using
function isValidDiscordId(id) {
return /^\d{17,19}$/.test(id); // Discord IDs are 17-19 digits
}

if (!isValidDiscordId(userId)) {
throw new Error('Invalid Discord ID');
}

3. Rate Limitingโ€‹

// โœ… Prevent abuse
const attempts = rewardAttempts.get(userId) || 0;
if (attempts > 10) {
throw new Error('Rate limit exceeded');
}
rewardAttempts.set(userId, attempts + 1);

4. Audit Loggingโ€‹

// โœ… Log all pregeneration attempts
await auditLog.create({
action: 'oauth_pregeneration',
provider,
userId,
amount,
timestamp: new Date(),
success: true
});

Summary for Non-Technical Peopleโ€‹

What this feature does:

  • Lets you reward users based on their social media accounts
  • Even if they've never logged into your app before
  • When they eventually log in, all their rewards are waiting

Why it's powerful:

  • Instant rewards (no login required first)
  • Viral growth (users discover rewards when they log in)
  • Frictionless onboarding (rewards waiting = incentive to join)

Setup time: 20 minutes (one-time) Maintenance: None (automatic)


Next Stepsโ€‹

  1. Get your Brand ID

    • Log into your Loyalteez dashboard
    • Copy your Brand ID
    • Add it to your .env file as VITE_LOYALTEEZ_BRAND_ID
  2. Choose your integration

  3. Implement pregeneration

    • Copy the code examples from this guide
    • Test with a real OAuth account (test credentials won't work)
    • Deploy to production
  4. Notify users

    • Tell them about their rewards
    • Include login link: https://loyalteez.app
    • Explain they need to log in with the same OAuth provider (Discord, Twitter, etc.)


Questions? Join our Discord: discord.gg/loyalteez

Need help? Email: [email protected]