Phoenix Gamification

Wallets

The Phoenix Wallet System provides a comprehensive multi-currency wallet for managing virtual currencies, loyalty points, and in-app balances. This guide covers everything you need to integrate wallet functionality into your application.

Authentication

All wallet endpoints support multiple authentication methods:

GatewayOperationEndpointsAuth Methods
Admin GatewayCurrency management/v1/tenants/{tenant_id}/wallet/currencies/*Admin JWT, API Key
Admin GatewayEarning rules/v1/tenants/{tenant_id}/wallet/earning-rules/*Admin JWT, API Key
Admin GatewayTier management/v1/tenants/{tenant_id}/wallet/tiers/*Admin JWT, API Key
Admin GatewayManual grant/deduct/v1/tenants/{tenant_id}/wallet/grant, /deductAdmin JWT, API Key
Query GatewayGet balances/v1/wallet/{tenant_id}/balancesTenant JWT, HMAC
Query GatewayTransaction history/v1/wallet/{tenant_id}/transactionsTenant JWT, HMAC
Query GatewayGet tier/v1/wallet/{tenant_id}/tierTenant JWT, HMAC

Recommended for frontend apps: Use JWT authentication. Your proxy server issues JWTs after user login, and the frontend calls Phoenix APIs directly with the token.

See Getting Started for authentication setup details.

Key Features

  • Multi-Currency Support: Define unlimited currency types per tenant (points, coins, gems, XP, etc.)
  • Balance Tracking: Per-user, per-currency available and lifetime balances
  • Transaction History: Immutable audit trail of all earnings and spendings
  • Earning Rules: Automatic currency grants based on events
  • Loyalty Tiers: Progression system based on lifetime earnings
  • Rate Limiting: Daily/weekly earning caps per rule
  • Spendable vs Non-Spendable: Mark currencies like XP as accumulate-only

Core Concepts

Currencies

Currencies are the foundation of the wallet system. Each tenant can define multiple currencies:

PropertyTypeDescription
idstringUnique ID (lowercase, underscores only)
namestringDisplay name
symbolstringOptional emoji or symbol
is_spendablebooleanIf false, currency only accumulates (like XP)
decimal_placesinteger0 for integers, 2 for cash-like values
activebooleanWhether currency is active

Example currencies:

[
  {
    "id": "loyalty_points",
    "name": "Loyalty Points",
    "symbol": "⭐",
    "is_spendable": true,
    "decimal_places": 0
  },
  {
    "id": "xp",
    "name": "Experience Points",
    "symbol": "✨",
    "is_spendable": false,
    "decimal_places": 0
  },
  {
    "id": "bonus_cash",
    "name": "Bonus Cash",
    "symbol": "$",
    "is_spendable": true,
    "decimal_places": 2
  }
]

Balances

Each user has two balance types per currency:

  • Available Balance: Current spendable amount
  • Lifetime Earned: Total earned over all time (never decreases)

Earning Rules

Earning rules automatically grant currency when specific events occur. Rules are evaluated in priority order.

PropertyTypeDescription
namestringDisplay name
currency_idstringCurrency to grant
event_typestringEvent type to match
event_filterobjectOptional filter expression
calculationobjectHow to calculate amount
max_per_eventintegerCap per single event
max_per_dayintegerDaily cap per user
max_per_weekintegerWeekly cap per user
priorityintegerHigher = evaluated first

Loyalty Tiers

Tiers provide a progression system based on lifetime currency earnings:

PropertyTypeDescription
tier_namestringDisplay name
tier_levelintegerNumeric level (1, 2, 3...)
min_lifetime_pointsintegerPoints required to reach tier
currency_idstringCurrency to track for progression
icon_urlstringBadge icon URL
badge_colorstringHex color code
benefitsobjectTier-specific benefits

User Endpoints (Query Gateway)

Get User Balances

Retrieve all currency balances for the authenticated user.

GET /v1/wallet/{tenant_id}/balances?user_id={user_id}

Response:

{
  "tenant_id": "your_tenant",
  "user_id": "user_123",
  "balances": [
    {
      "currency_id": "gold_coins",
      "currency_name": "Gold Coins",
      "currency_symbol": "🪙",
      "available": 1500,
      "lifetime_earned": 5000
    },
    {
      "currency_id": "gems",
      "currency_name": "Premium Gems",
      "currency_symbol": "💎",
      "available": 25,
      "lifetime_earned": 100
    }
  ]
}

Get Transaction History

Retrieve transaction history for a user.

GET /v1/wallet/{tenant_id}/transactions?user_id={user_id}

Query Parameters:

ParameterTypeDescription
user_idstringRequired. User ID
currency_idstringFilter by currency (optional)
limitintegerPage size (default: 100, max: 1000)
offsetintegerPage offset (default: 0)

Response:

[
  {
    "id": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
    "currency_id": "gold_coins",
    "amount": 100,
    "balance_after": 1500,
    "source_type": "streak_milestone",
    "source_ref": "streak_abc",
    "description": "7-day login streak milestone",
    "created_at": "2026-01-26T10:30:00Z"
  },
  {
    "id": "b2c3d4e5-6789-01ab-cdef-2345678901bc",
    "currency_id": "gold_coins",
    "amount": -50,
    "balance_after": 1400,
    "source_type": "wheel_spin",
    "source_ref": "wheel_xyz",
    "description": "Spin cost for Daily Wheel",
    "created_at": "2026-01-25T15:45:00Z"
  }
]

Get User Tier

Retrieve the user's current loyalty tier and progress.

GET /v1/wallet/{tenant_id}/tier?user_id={user_id}

Response:

{
  "tier_id": "tier-gold",
  "tier_name": "Gold",
  "tier_level": 3,
  "lifetime_points": 5000,
  "next_tier_name": "Platinum",
  "next_tier_points": 10000,
  "points_to_next": 5000
}

Returns null if no tier is configured or user hasn't earned any points.

Admin Endpoints (Admin Gateway)

Currency Management

GET    /v1/tenants/{tenant_id}/wallet/currencies
POST   /v1/tenants/{tenant_id}/wallet/currencies
GET    /v1/tenants/{tenant_id}/wallet/currencies/{currency_id}
PUT    /v1/tenants/{tenant_id}/wallet/currencies/{currency_id}
DELETE /v1/tenants/{tenant_id}/wallet/currencies/{currency_id}

Create Currency Request:

{
  "id": "loyalty_points",
  "name": "Loyalty Points",
  "symbol": "⭐",
  "is_spendable": true,
  "decimal_places": 0
}

Earning Rules Management

GET    /v1/tenants/{tenant_id}/wallet/earning-rules
POST   /v1/tenants/{tenant_id}/wallet/earning-rules
GET    /v1/tenants/{tenant_id}/wallet/earning-rules/{rule_id}
PUT    /v1/tenants/{tenant_id}/wallet/earning-rules/{rule_id}
DELETE /v1/tenants/{tenant_id}/wallet/earning-rules/{rule_id}

Create Earning Rule Request:

{
  "name": "Wager Points",
  "currency_id": "loyalty_points",
  "event_type": "bet.settled",
  "event_filter": {
    "op": "gte",
    "field": "amount",
    "value": 100
  },
  "calculation": {
    "type": "per_unit",
    "points": 1,
    "per": 100,
    "field": "amount"
  },
  "max_per_day": 1000,
  "max_per_week": 5000,
  "priority": 10
}

Calculation Types

Fixed Amount:

{
  "type": "fixed",
  "amount": 10
}

Per Unit (e.g., 1 point per $10 wagered):

{
  "type": "per_unit",
  "points": 1,
  "per": 1000,
  "field": "amount"
}

Percentage (e.g., 0.1% of transaction):

{
  "type": "percentage",
  "rate": 0.001,
  "field": "amount"
}

Manual Grant/Deduct

Grant Currency:

POST /v1/tenants/{tenant_id}/wallet/grant
{
  "user_id": "user_123",
  "currency_id": "gold_coins",
  "amount": 100,
  "source_type": "promotion",
  "source_ref": "promo_jan2026",
  "description": "January promotion bonus",
  "idempotency_key": "promo_jan2026_user123"
}

Deduct Currency:

POST /v1/tenants/{tenant_id}/wallet/deduct
{
  "user_id": "user_123",
  "currency_id": "gold_coins",
  "amount": 50,
  "source_type": "manual_adjustment",
  "source_ref": "ticket_12345",
  "description": "Customer support adjustment",
  "idempotency_key": "adjustment_ticket_12345"
}

Tier Management

GET    /v1/tenants/{tenant_id}/wallet/tiers
POST   /v1/tenants/{tenant_id}/wallet/tiers
GET    /v1/tenants/{tenant_id}/wallet/tiers/{tier_id}
PUT    /v1/tenants/{tenant_id}/wallet/tiers/{tier_id}
DELETE /v1/tenants/{tenant_id}/wallet/tiers/{tier_id}

Create Tier Request:

{
  "tier_name": "Gold",
  "tier_level": 3,
  "min_lifetime_points": 5000,
  "currency_id": "loyalty_points",
  "badge_color": "#FFD700",
  "benefits": {
    "earning_multiplier": 1.25,
    "daily_spin": 3,
    "exclusive_wheels": true
  }
}

JavaScript Integration Example

class WalletClient {
  constructor(apiUrl, jwt) {
    this.apiUrl = apiUrl;
    this.jwt = jwt;
  }

  async getBalances(tenantId, userId) {
    const response = await fetch(
      `${this.apiUrl}/v1/wallet/${tenantId}/balances?user_id=${userId}`,
      {
        headers: {
          'Authorization': `Bearer ${this.jwt}`,
          'Content-Type': 'application/json'
        }
      }
    );
    
    if (!response.ok) {
      throw new Error('Failed to fetch balances');
    }
    
    return response.json();
  }

  async getTransactions(tenantId, userId, options = {}) {
    const params = new URLSearchParams({
      user_id: userId,
      ...options
    });
    
    const response = await fetch(
      `${this.apiUrl}/v1/wallet/${tenantId}/transactions?${params}`,
      {
        headers: {
          'Authorization': `Bearer ${this.jwt}`,
          'Content-Type': 'application/json'
        }
      }
    );
    
    return response.json();
  }

  async getTier(tenantId, userId) {
    const response = await fetch(
      `${this.apiUrl}/v1/wallet/${tenantId}/tier?user_id=${userId}`,
      {
        headers: {
          'Authorization': `Bearer ${this.jwt}`,
          'Content-Type': 'application/json'
        }
      }
    );
    
    return response.json();
  }
}

// Example usage
const wallet = new WalletClient('https://query.phoenix.example.com', userJwt);

// Get user's balances
const { balances } = await wallet.getBalances('tenant_abc', 'user_123');
const goldCoins = balances.find(b => b.currency_id === 'gold_coins')?.available || 0;
console.log(`Gold Coins: ${goldCoins}`);

// Get tier info
const tier = await wallet.getTier('tenant_abc', 'user_123');
if (tier) {
  console.log(`Current tier: ${tier.tier_name}`);
  console.log(`Points to ${tier.next_tier_name}: ${tier.points_to_next}`);
}

Integration with Other Services

The Wallet automatically integrates with other Phoenix gamification services:

ServiceIntegrationHow it works
StreakAutomaticStreak milestones with currency rewards call wallet.earn()
CouponAutomaticCoupon redemption with currency reward calls wallet.earn()
LeaderboardAutomaticLeaderboard window completion grants rewards via wallet.earn()
Spin WheelAutomaticWheel spin_cost deducts via wallet.spend(), currency prizes call wallet.earn()
Rewards StoreAutomaticPurchases deduct via wallet.spend()

Webhooks

Configure webhooks to receive real-time wallet event notifications.

Event Types

EventDescription
currency.earnedCurrency added to user balance
currency.spentCurrency deducted from user balance
tier.changedUser's loyalty tier changed

Webhook Payload

{
  "event_type": "currency.earned",
  "tenant_id": "your_tenant",
  "user_id": "user_123",
  "timestamp": "2026-01-26T10:30:00Z",
  "data": {
    "currency_id": "gold_coins",
    "amount": 100,
    "new_balance": 1500,
    "source_type": "streak_milestone",
    "transaction_id": "txn_abc123"
  }
}

Webhooks include an X-Webhook-Signature header with an HMAC-SHA256 signature of the payload.

Best Practices

  1. Currency Design: Start with a single primary currency, add more as needed
  2. Earning Rules: Set reasonable daily/weekly caps to prevent abuse
  3. Tier Progression: Make tiers achievable but meaningful
  4. Idempotency Keys: Always provide unique keys for grants/deducts to prevent duplicates
  5. Non-Spendable Currencies: Use for XP/reputation that shouldn't decrease
  6. Balance Caching: Cache balances client-side but refresh on critical actions

Next Steps