Cloudflare Workers Deployment
Complete guide for deploying Loyalteez Cloudflare Workers to production with mainnet configurations.
Overview
Loyalteez infrastructure runs on 4 Cloudflare Workers that handle API requests, wallet creation, reward processing, and gasless transactions. This guide covers deployment, configuration, and troubleshooting.
Workers Overview:
| Worker | Domain | Purpose |
|---|---|---|
| Event Handler | api.loyalteez.app | Manual events, Stripe minting, SDK serving |
| Pregeneration | register.loyalteez.app | Email/OAuth wallet creation via Privy |
| Reward Processor | Scheduled (Cron) | Background reward distribution |
| Gas Relayer | relayer.loyalteez.app | Gasless transaction execution |
Prerequisites
# Install Wrangler CLI
npm install -g wrangler
# Authenticate with Cloudflare
wrangler login
# Verify authentication
wrangler whoami
Mainnet Configuration
All workers are configured for Soneium Mainnet (Chain ID: 1868).
Network Settings
CHAIN_ID=1868
NETWORK_NAME=soneium
RPC_URL=https://rpc.soneium.org
BLOCK_EXPLORER=https://soneium.blockscout.com/
Contract Addresses (Mainnet - Deployed 2024)
# LTZ Token (EIP-2612 Permit enabled)
LOYALTEEZ_ADDRESS=0x5242b6DB88A72752ac5a54cFe6A7DB8244d743c9
# Perk NFT (v6 - no salt requirement)
PERK_NFT_ADDRESS=0x6ae30d6Dcf3e75456B6582b057f1Bf98A90F2CA0
# Points Sale (V3 with Permit)
POINTS_SALE_ADDRESS=0x5269B83F6A4E31bEdFDf5329DC052FBb661e3c72
# USDC (Mainnet)
USDC_ADDRESS=0xbA9986D2381edf1DA03B0B9c1f8b00dc4AacC369
Worker 1: Event Handler
Handles main API endpoints and SDK serving.
Routes
api.loyalteez.app/*reward.loyalteez.app/*
Endpoints
# Health Check
GET https://api.loyalteez.app/health
# Manual Event Submission
POST https://api.loyalteez.app/loyalteez-api/manual-event
# Stripe Minting
POST https://api.loyalteez.app/loyalteez-api/stripe-mint
# SDK Serving
GET https://api.loyalteez.app/sdk.js
Configuration (wrangler.event-handler.toml)
name = "loyalteez-event-handler"
main = "src/event-handler/index.js"
compatibility_date = "2024-10-01"
compatibility_flags = ["nodejs_compat"]
[vars]
ENVIRONMENT = "production"
CHAIN_ID = "1868"
NETWORK_NAME = "soneium"
RPC_URL = "https://rpc.soneium.org"
BLOCK_EXPLORER = "https://soneium.blockscout.com/"
LOYALTEEZ_ADDRESS = "0x5242b6DB88A72752ac5a54cFe6A7DB8244d743c9"
PERK_NFT_ADDRESS = "0x6ae30d6Dcf3e75456B6582b057f1Bf98A90F2CA0"
POINTS_SALE_ADDRESS = "0x5269B83F6A4E31bEdFDf5329DC052FBb661e3c72"
PRIVY_APP_ID = "your_privy_app_id"
SUPABASE_URL = "https://your-project.supabase.co"
[[routes]]
pattern = "api.loyalteez.app/*"
zone_name = "loyalteez.app"
[[routes]]
pattern = "reward.loyalteez.app/*"
zone_name = "loyalteez.app"
Secrets (Encrypted)
# Set via wrangler secret put
wrangler secret put PRIVY_SECRET --config wrangler.event-handler.toml
wrangler secret put SUPABASE_SECRET_KEY --config wrangler.event-handler.toml
wrangler secret put WALLET_PRIVATE_KEY --config wrangler.event-handler.toml
wrangler secret put STRIPE_SECRET_KEY --config wrangler.event-handler.toml
wrangler secret put RESEND_API_KEY --config wrangler.event-handler.toml
Deploy
cd cloudflare-automation
npx wrangler deploy --config wrangler.event-handler.toml
Health Check Fix (Jan 2025)
Issue: Worker only responded to /loyalteez-api/health, not root /health.
Solution: Add root-level health check before API routing:
// src/event-handler/index.js
if (path === '/health' && request.method === 'GET') {
return successResponse({
status: 'healthy',
service: 'event-handler',
timestamp: new Date().toISOString(),
hostname: url.hostname,
routes: ['api.loyalteez.app/*', 'reward.loyalteez.app/*']
}, request);
}
Worker 2: Pregeneration
Creates Privy wallets for email addresses and OAuth users.
Route
register.loyalteez.app/*
Endpoints
# Health Check
GET https://register.loyalteez.app/health
# Wallet Pregeneration
POST https://register.loyalteez.app/loyalteez-api/pregenerate-user
Configuration (wrangler.pregeneration.toml)
name = "loyalteez-pregeneration"
main = "src/pregeneration/index.js"
compatibility_date = "2024-10-01"
compatibility_flags = ["nodejs_compat"]
[vars]
ENVIRONMENT = "production"
PRIVY_APP_ID = "your_privy_app_id"
SUPABASE_PUBLISH_KEY = "sb_publishable_..."
[[routes]]
pattern = "register.loyalteez.app/*"
zone_name = "loyalteez.app"
Secrets
wrangler secret put PRIVY_APP_SECRET --config wrangler.pregeneration.toml
wrangler secret put SUPABASE_URL --config wrangler.pregeneration.toml
wrangler secret put SUPABASE_SECRET_KEY --config wrangler.pregeneration.toml
Deploy
npx wrangler deploy --config wrangler.pregeneration.toml
Worker 3: Reward Processor
Background worker that processes pending rewards every minute.
Type
Scheduled Worker (Cron: */1 * * * *)
Endpoints
# Health Check (via workers.dev URL)
GET https://loyalteez-reward-processor.taylor-cox1199.workers.dev/health
# Manual Processing
POST https://loyalteez-reward-processor.taylor-cox1199.workers.dev/process-pending
# Queue Status
GET https://loyalteez-reward-processor.taylor-cox1199.workers.dev/queue
Configuration (wrangler.reward-processor.toml)
name = "loyalteez-reward-processor"
main = "src/reward-processor/index.js"
compatibility_date = "2023-11-01"
[vars]
ENVIRONMENT = "production"
CHAIN_ID = "1868"
RPC_URL = "https://rpc.soneium.org"
LOYALTEEZ_ADDRESS = "0x5242b6DB88A72752ac5a54cFe6A7DB8244d743c9"
PRIVY_APP_ID = "your_privy_app_id"
# Scheduled trigger - every 1 minute
[triggers]
crons = ["*/1 * * * *"]
# Note: No custom route - handled by Event Handler at reward.loyalteez.app
Secrets
wrangler secret put PRIVY_SECRET --config wrangler.reward-processor.toml
wrangler secret put SUPABASE_SECRET_KEY --config wrangler.reward-processor.toml
wrangler secret put WALLET_PRIVATE_KEY --config wrangler.reward-processor.toml
wrangler secret put RESEND_API_KEY --config wrangler.reward-processor.toml
Deploy
npx wrangler deploy --config wrangler.reward-processor.toml
Worker 4: Gas Relayer
Executes gasless transactions on behalf of users.
Route
relayer.loyalteez.app/*
Endpoints
# Health Check
GET https://relayer.loyalteez.app/health
# Relay Transaction (requires Privy auth)
POST https://relayer.loyalteez.app/relay
Configuration (wrangler-gas-relayer.toml)
name = "loyalteez-gas-relayer"
main = "src/gas-relayer/worker.js"
compatibility_date = "2024-10-01"
[vars]
VITE_RPC_URL = "https://rpc.soneium.org"
VITE_PRIVY_APP_ID = "your_privy_app_id"
DEV_WALLET_ADDRESS = "0x92627010DF82F6aA16eeCb45C4bD2207140C52A7"
VITE_LOYALTEEZ_ADDRESS = "0x5242b6DB88A72752ac5a54cFe6A7DB8244d743c9"
VITE_PERK_NFT_ADDRESS = "0x6ae30d6Dcf3e75456B6582b057f1Bf98A90F2CA0"
VITE_POINTS_SALE_ADDRESS = "0x5269B83F6A4E31bEdFDf5329DC052FBb661e3c72"
[[kv_namespaces]]
binding = "RELAYER_KV"
id = "your_kv_namespace_id"
[env.production]
name = "loyalteez-gas-relayer"
routes = [
{ pattern = "relayer.loyalteez.app/*", zone_name = "loyalteez.app" }
]
Secrets
wrangler secret put DEV_WALLET_PRIVATE_KEY --config wrangler-gas-relayer.toml
wrangler secret put PRIVY_SECRET --config wrangler-gas-relayer.toml
wrangler secret put VITE_SUPABASE_SECRET_KEY --config wrangler-gas-relayer.toml
wrangler secret put VITE_SUPABASE_PUBLISH_KEY --config wrangler-gas-relayer.toml
Deploy
npx wrangler deploy --config wrangler-gas-relayer.toml --env production
Automated Deployment
Use the provided deployment script to deploy all workers at once.
deploy-workers.sh
#!/bin/bash
# Deploy all workers to production
echo "🚀 Deploying all Cloudflare Workers..."
# 1. Event Handler
echo "📦 [1/4] Deploying Event Handler..."
npx wrangler deploy --config wrangler.event-handler.toml
# 2. Pregeneration
echo "📦 [2/4] Deploying Pregeneration..."
npx wrangler deploy --config wrangler.pregeneration.toml
# 3. Reward Processor
echo "📦 [3/4] Deploying Reward Processor..."
npx wrangler deploy --config wrangler.reward-processor.toml
# 4. Gas Relayer
echo "📦 [4/4] Deploying Gas Relayer..."
npx wrangler deploy --config wrangler-gas-relayer.toml --env production
echo "✅ All workers deployed successfully!"
Usage
cd cloudflare-automation
chmod +x deploy-workers.sh
./deploy-workers.sh
Health Check Verification
After deployment, verify all workers are healthy:
# Event Handler
curl https://api.loyalteez.app/health
# Expected: {"status":"healthy","service":"event-handler",...}
# Pregeneration
curl https://register.loyalteez.app/health
# Expected: {"status":"healthy","service":"oauth-pregeneration",...}
# Gas Relayer
curl https://relayer.loyalteez.app/health
# Expected: {"status":"healthy","service":"gas-relayer",...}
# Reward Processor (workers.dev URL)
curl https://loyalteez-reward-processor.taylor-cox1199.workers.dev/health
# Expected: {"status":"healthy","worker":"reward-processor",...}
Monitoring & Logs
Tail Logs in Real-Time
# Event Handler
wrangler tail loyalteez-event-handler
# Pregeneration
wrangler tail loyalteez-pregeneration
# Reward Processor
wrangler tail loyalteez-reward-processor
# Gas Relayer
wrangler tail loyalteez-gas-relayer
View Deployment History
# List deployments
wrangler deployments list --name loyalteez-event-handler
# View specific deployment
wrangler deployments view <deployment-id>
Check Worker Status
# Worker details
wrangler status --name loyalteez-event-handler
# KV namespace list
wrangler kv:namespace list
# KV namespace keys
wrangler kv:key list --namespace-id <namespace-id>
Troubleshooting
404 on Health Endpoints
Symptoms:
curl https://api.loyalteez.app/healthreturns 404- Worker shows as deployed in dashboard
Causes:
- Worker only has
/loyalteez-api/health, not root/health - DNS propagation delay (5-30 minutes)
- Route not configured
Solutions:
- Add root-level health check (Fixed in Jan 2025):
// Before API routing
if (path === '/health' && request.method === 'GET') {
return successResponse({
status: 'healthy',
service: 'event-handler',
timestamp: new Date().toISOString()
}, request);
}
- Wait for DNS propagation:
# Check DNS
nslookup api.loyalteez.app
# Test via workers.dev URL while waiting
curl https://loyalteez-event-handler.taylor-cox1199.workers.dev/health
- Verify routes in dashboard:
- Cloudflare Dashboard → Workers & Pages → loyalteez-event-handler
- Check "Domains & Routes" tab
Route Conflicts
Error: "A route with the same pattern already exists"
Solution: Remove duplicate route from wrangler.toml:
# BAD: Both workers define reward.loyalteez.app/*
# In event-handler:
[[routes]]
pattern = "reward.loyalteez.app/*"
# In reward-processor:
[[routes]]
pattern = "reward.loyalteez.app/*" # ❌ CONFLICT!
# GOOD: Only Event Handler serves reward.loyalteez.app
# reward-processor has no custom route (runs on schedule)
Secrets Not Working
Symptoms:
- Worker deploys but fails at runtime
- Authentication errors in logs
Solution:
# List existing secrets
wrangler secret list --config wrangler.event-handler.toml
# Set missing secrets
wrangler secret put PRIVY_SECRET --config wrangler.event-handler.toml
# Redeploy after setting secrets
npx wrangler deploy --config wrangler.event-handler.toml
Cron Not Firing
Symptoms:
- Reward Processor not running
- No scheduled executions in logs
Solution:
- Verify cron trigger in wrangler.toml:
[triggers]
crons = ["*/1 * * * *"] # Every minute
- Check scheduled logs:
wrangler tail loyalteez-reward-processor --format pretty
- Manually trigger:
curl -X POST https://loyalteez-reward-processor.taylor-cox1199.workers.dev/process-pending
Security Best Practices
1. Never Commit Secrets
# ✅ Good - stored encrypted
wrangler secret put PRIVY_SECRET
# ❌ Bad - visible in code
[vars]
PRIVY_SECRET = "actual_secret_value"
2. Use Environment-Specific Configs
# Production
[env.production]
name = "loyalteez-gas-relayer"
routes = [
{ pattern = "relayer.loyalteez.app/*", zone_name = "loyalteez.app" }
]
# Development
[env.development]
name = "loyalteez-gas-relayer-dev"
# No custom routes - use workers.dev URL
3. Restrict CORS Origins
// Development
const corsHeaders = {
'Access-Control-Allow-Origin': '*'
};
// Production
const corsHeaders = {
'Access-Control-Allow-Origin': 'https://partners.loyalteez.app'
};
4. Rate Limiting
Use KV namespaces for rate limiting:
const rateLimit = async (userAddress, limit = 35) => {
const key = `rate_limit_${userAddress}_${Math.floor(Date.now() / 3600000)}`;
const count = await env.RELAYER_KV.get(key) || 0;
if (count >= limit) {
throw new Error('Rate limit exceeded');
}
await env.RELAYER_KV.put(key, count + 1, { expirationTtl: 3600 });
};
Performance Optimization
1. Use KV for Caching
// Cache DNS lookups
const cachedDNS = await env.DNS_CACHE.get(`dns_${domain}`);
if (cachedDNS) {
return JSON.parse(cachedDNS);
}
const dnsRecord = await fetchDNSTxtRecord(domain);
await env.DNS_CACHE.put(`dns_${domain}`, JSON.stringify(dnsRecord), {
expirationTtl: 3600 // 1 hour
});
2. Batch Database Queries
// BAD: N+1 queries
for (const event of events) {
await database.getEvent(event.id);
}
// GOOD: Single batch query
const eventIds = events.map(e => e.id);
const eventData = await database.batchGetEvents(eventIds);
3. Use Durable Objects Sparingly
Durable Objects are powerful but expensive. Use KV for:
- Simple key-value storage
- Rate limiting
- Caching
Use Durable Objects for:
- Real-time collaboration
- Strong consistency requirements
- Coordination between workers
Rollback Strategy
Rollback to Previous Version
# List deployments
wrangler deployments list --name loyalteez-event-handler
# Rollback to specific version
wrangler rollback --name loyalteez-event-handler --version <version-id>
# Verify rollback
curl https://api.loyalteez.app/health
Gradual Rollout
# Deploy to 10% of traffic first
[env.canary]
name = "loyalteez-event-handler-canary"
routes = [
{ pattern = "api.loyalteez.app/*", zone_name = "loyalteez.app", percentage = 10 }
]
Related Documentation
- Environment Variables - Configuration reference
- API Reference - API endpoints
- Testing Guide - Test your workers
- Authentication - Privy integration
Changelog
January 2025
- ✅ Added root-level
/healthendpoint to Event Handler - ✅ Verified mainnet configurations (Chain ID: 1868)
- ✅ Fixed route conflicts (reward.loyalteez.app only on Event Handler)
- ✅ Updated contract addresses for 2024 mainnet deployment
- ✅ Created automated deployment script
- ✅ Documented health check verification
December 2024
- Initial Cloudflare Workers documentation
Need Help? Join our Discord or email [email protected]