Pregeneration API
Create user wallets linked to OAuth accounts before users log in.
🎉 Production Ready: This API is fully operational and battle-tested. It's idempotent, handles both new and existing users gracefully, and requires no Privy credentials from you.
Overview
The Pregeneration API allows you to create wallets for users based on their social media identities (Discord, Twitter, GitHub, etc.) before they log into your app. This enables instant rewards and viral growth mechanics.
No Setup Required: Just call our API - we handle all Privy integration server-side for you!
Production Base URL:
https://register.loyalteez.app
Full Endpoint:
https://register.loyalteez.app/loyalteez-api/pregenerate-user
Alternative (Workers.dev):
https://loyalteez-pregeneration.taylor-cox1199.workers.dev/loyalteez-api/pregenerate-user
POST /loyalteez-api/pregenerate-user
Create a wallet linked to a user's OAuth account.
Request
Headers:
Content-Type: application/json
Body:
{
"brand_id": "your-brand-id",
"oauth_provider": "discord",
"oauth_user_id": "123456789012345678",
"oauth_username": "gamer123",
"metadata": {
"source": "game_bot",
"campaign": "launch_week"
}
}
Parameters
| Field | Type | Required | Description |
|---|---|---|---|
brand_id | string | Yes | Your Loyalteez Brand ID |
oauth_provider | string | Yes | OAuth provider: discord, twitter, github, google, telegram, spotify, instagram, or tiktok |
oauth_user_id | string | Yes | User's unique ID on the OAuth platform (must be real OAuth ID from actual account) |
oauth_username | string | Recommended | User's username on that platform (validated against real provider formats) |
metadata | object | No | Additional data to store with the pregeneration event |
OAuth User ID Formats
| Provider | ID Format | Example | Where to Find |
|---|---|---|---|
| Discord | 17-20 digit snowflake ID | "123456789012345678" | user.id from Discord API |
| Twitter/X | Numeric string | "987654321" | user.id_str from Twitter API |
| GitHub | Numeric string | "45678901" | user.id from GitHub API |
| Long numeric string | "108012345678901234567" | sub claim from OAuth token | |
| Telegram | Numeric | "123456789" | user.id from Telegram Bot API |
| Spotify | Alphanumeric string | "abc123xyz" | user.id from Spotify API |
| Numeric string | "123456789" | user.id from Instagram Graph API | |
| TikTok | Alphanumeric string | "abc123" | open_id from TikTok API |
Important: All IDs must be from real OAuth accounts. The system validates credentials with the actual OAuth providers and rejects test/fake accounts for security.
Response
Success (200 - New User):
{
"success": true,
"wallet_address": "0x1234567890123456789012345678901234567890",
"privy_user_id": "did:privy:clpr_abc123...",
"oauth_provider": "discord",
"oauth_user_id": "123456789012345678",
"created_new": true,
"message": "Wallet created for gamer123",
"next_steps": [
"Use wallet_address to reward user with LTZ",
"User will see rewards when they log in with discord"
]
}
Success (200 - Existing User):
{
"success": true,
"wallet_address": "0x1234567890123456789012345678901234567890",
"privy_user_id": "did:privy:clpr_abc123...",
"oauth_provider": "discord",
"oauth_user_id": "123456789012345678",
"created_new": false,
"message": "Wallet retrieved for existing user gamer123",
"next_steps": [
"Use wallet_address to reward user with LTZ",
"User will see rewards when they log in with discord"
]
}
Note: The
created_newfield indicates whether a new wallet was created (true) or an existing wallet was retrieved (false). The endpoint is idempotent - calling it multiple times with the same OAuth credentials returns the same wallet address.
Error (400 - Missing Fields):
{
"error": "Missing required fields",
"required": [
"brand_id",
"oauth_provider",
"oauth_user_id"
]
}
Error (400 - Invalid Provider):
{
"error": "Invalid OAuth provider",
"valid_providers": [
"discord",
"twitter",
"github",
"google",
"telegram"
]
}
Error (400 - Invalid Discord ID):
{
"error": "Invalid Discord user ID",
"details": "Discord user IDs must be 17-20 digit numeric strings (snowflake IDs)",
"example": "123456789012345678"
}
Error (400 - Invalid OAuth Credentials):
{
"error": "Invalid OAuth credentials",
"hint": "This endpoint requires REAL OAuth user IDs and usernames from actual discord accounts. Test credentials are rejected by Privy for security.",
"provider": "discord"
}
Error (429 - Rate Limited):
{
"error": "Rate limit exceeded, please try again later"
}
Error (500 - Server Error):
{
"error": "Internal server error",
"details": "Failed to create wallet"
}
Security Note: The pregeneration endpoint validates all OAuth credentials with the actual OAuth providers (Discord, Twitter, GitHub, etc.) to ensure accounts are real. Test/fake credentials will be rejected with helpful error messages.
How It Works
1. Idempotent Wallet Creation
Same OAuth ID = Same Wallet (Always)
The endpoint is idempotent - calling it multiple times with the same OAuth credentials always returns the same wallet address, whether it's creating a new wallet or retrieving an existing one.
// First call - Creates new wallet
POST /pregenerate-user { oauth_provider: "discord", oauth_user_id: "123" }
// Response: { wallet_address: "0xABC...", created_new: true }
// Second call - Returns existing wallet
POST /pregenerate-user { oauth_provider: "discord", oauth_user_id: "123" }
// Response: { wallet_address: "0xABC...", created_new: false }
// User logs in with Discord
// Gets same wallet: 0xABC...
This means:
- ✅ Safe to call multiple times (no duplicate wallets)
- ✅ No need to check if wallet exists first
- ✅ Perfect for reward failsafe systems
- ✅
created_newflag tells you what happened
This is by design - wallets are permanently linked to OAuth accounts for security and consistency.
2. Cross-Platform Considerations
Different OAuth providers = Different wallets:
// Pregenerate with Discord
POST { oauth_provider: "discord", oauth_user_id: "123" }
// Creates wallet A
// Pregenerate with Twitter (same person!)
POST { oauth_provider: "twitter", oauth_user_id: "456" }
// Creates wallet B (different!)
Best practice: Choose one primary OAuth provider per user and always use that.
3. Login Flow
1. You pregenerate wallet for discord:123
2. You award 100 LTZ to that wallet
3. User logs into Loyalteez with Discord
4. Privy recognizes discord:123
5. User sees their pregenerated wallet + 100 LTZ! 🎉
Complete Example Flow
Step 1: Pregenerate Wallet
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"
}'
Response:
{
"success": true,
"wallet_address": "0xABC123...",
"privy_user_id": "did:privy:...",
"message": "Wallet pregenerated for gamer123"
}
Step 2: Award LTZ
curl -X POST https://api.loyalteez.app/loyalteez-api/manual-event \
-H "Content-Type: application/json" \
-d '{
"event": "pregeneration_reward",
"email": "[email protected]",
"metadata": {
"brand_id": "your-brand-id",
"wallet_address": "0xABC123...",
"reward_amount": 100
}
}'
Step 3: Notify User
// In your Discord bot
message.reply(
`🎉 You earned 100 LTZ! ` +
`Log in at https://loyalteez.app with Discord to see your rewards!\n` +
`Your wallet: ${wallet_address}`
);
Rate Limits
| Limit | Value | Scope |
|---|---|---|
| Pregeneration requests | 100 per minute | Per brand |
| Daily pregenerations | 10,000 | Per brand |
If you need higher limits, contact [email protected]
Idempotency & Failsafe Behavior
Calling the pregeneration endpoint multiple times with the same oauth_provider + oauth_user_id:
- ✅ Returns the same wallet address every time
- ✅ Does NOT create duplicate wallets
- ✅ Safe to call multiple times (no side effects)
- ✅
created_newfield indicates new vs. existing wallet
Perfect for reward failsafe systems:
// Developer pattern: Always call pregeneration, don't care if new or existing
async function rewardUser(provider, userId, amount) {
// Step 1: Get wallet (creates if new, retrieves if existing)
const { wallet_address, created_new } = await pregenerateWallet(provider, userId);
console.log(created_new ? 'New user!' : 'Returning user!');
// Step 2: Send rewards to wallet
await sendLTZ(wallet_address, amount);
}
This means:
- You can retry failed requests safely
- No need to check if wallet exists first
- Simplifies error handling
- Works as a "get or create" pattern
Best Practices
✅ DO
-
Call from your backend/server
// ✅ Server-side (secure)
await fetch('https://register.loyalteez.app/loyalteez-api/pregenerate-user', {
method: 'POST',
body: JSON.stringify({ brand_id, oauth_provider, oauth_user_id })
}); -
Validate OAuth IDs
// ✅ Validate format
function isValidDiscordId(id) {
return /^\d{17,19}$/.test(id);
} -
Store the provider you used
// ✅ Remember which OAuth provider
database.saveUser({
userId: '123',
primaryOAuthProvider: 'discord', // Store this!
oauthUserId: '456'
}); -
Handle errors gracefully
// ✅ Don't break if pregeneration fails
try {
await pregenerateWallet(...);
} catch (error) {
console.error('Pregeneration failed:', error);
// Queue for retry or log for manual review
}
❌ DON'T
-
Don't call from frontend/browser
// ❌ NEVER - users could pregenerate for anyone!
// This should only happen server-side -
Don't use email/username as ID
// ❌ WRONG - These can change
oauth_user_id: user.email
oauth_user_id: user.username
// ✅ CORRECT - Platform IDs never change
oauth_user_id: user.id -
Don't mix OAuth providers
// ❌ Inconsistent - creates different wallets
pregenerate('discord', '123'); // Wallet A
pregenerate('twitter', '456'); // Wallet B (different person!)
// ✅ Consistent - same provider
const provider = getUserPrimaryProvider(userId);
pregenerate(provider, oauthId);
Testing
Test Endpoint
curl -X POST https://register.loyalteez.app/loyalteez-api/pregenerate-user \
-H "Content-Type: application/json" \
-d '{
"brand_id": "test-brand",
"oauth_provider": "discord",
"oauth_user_id": "test_'$(date +%s)'",
"oauth_username": "test_user"
}'
Verify Wallet Creation
- Note the
wallet_addressfrom response - Check on block explorer:
https://soneium.blockscout.com/address/{wallet_address} - Log into Loyalteez with the OAuth account
- Verify you see the pregenerated wallet
Security
Server-Side Only
⚠️ CRITICAL: Only call this endpoint from your backend server, never from browser/frontend code.
Why?
- If exposed to frontend, users could pregenerate wallets for anyone
- Could be abused to create spam accounts
- No authentication on this endpoint (by design for simplicity)
Solution:
- Keep pregeneration logic on your server
- Validate user identity before calling
- Rate limit your own API
Audit Logging
All pregeneration events are logged with:
- Brand ID
- OAuth provider & user ID
- Timestamp
- Wallet address created
- Metadata provided
This helps with:
- Debugging issues
- Tracking abuse
- Compliance
- Analytics
Troubleshooting
"Invalid OAuth user ID format"
Cause: Wrong ID type or format
Fix:
// Discord: Must be string of 17-19 digits
oauth_user_id: String(discordUser.id) // ✅
// Twitter: Must be string
oauth_user_id: twitterUser.id_str // ✅
// GitHub: Convert number to string
oauth_user_id: String(githubUser.id) // ✅
"Rate limit exceeded"
Cause: Too many requests in short time
Fix:
- Implement caching (cache wallet addresses for 1 hour)
- Add request debouncing
- Contact us for higher limits if needed
"Wallet not showing for user"
Cause: User logged in with different OAuth provider
Fix:
// Make sure you use same provider
const storedProvider = database.getUserProvider(userId);
await pregenerate(storedProvider, oauthId); // ✅ Consistent
Related Documentation
- OAuth Pregeneration Guide - Complete implementation guide
- Event Handler API - Reward users with LTZ
- Discord Integration - Discord bot setup
- Custom Integration - Other platforms
Support
Questions? Email [email protected]
Need higher rate limits? Contact us at [email protected]
Found a bug? Report at [email protected]