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:
| Gateway | Operation | Endpoints | Auth Methods |
|---|---|---|---|
| Admin Gateway | Currency management | /v1/tenants/{tenant_id}/wallet/currencies/* | Admin JWT, API Key |
| Admin Gateway | Earning rules | /v1/tenants/{tenant_id}/wallet/earning-rules/* | Admin JWT, API Key |
| Admin Gateway | Tier management | /v1/tenants/{tenant_id}/wallet/tiers/* | Admin JWT, API Key |
| Admin Gateway | Manual grant/deduct | /v1/tenants/{tenant_id}/wallet/grant, /deduct | Admin JWT, API Key |
| Query Gateway | Get balances | /v1/wallet/{tenant_id}/balances | Tenant JWT, HMAC |
| Query Gateway | Transaction history | /v1/wallet/{tenant_id}/transactions | Tenant JWT, HMAC |
| Query Gateway | Get tier | /v1/wallet/{tenant_id}/tier | Tenant 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:
| Property | Type | Description |
|---|---|---|
id | string | Unique ID (lowercase, underscores only) |
name | string | Display name |
symbol | string | Optional emoji or symbol |
is_spendable | boolean | If false, currency only accumulates (like XP) |
decimal_places | integer | 0 for integers, 2 for cash-like values |
active | boolean | Whether 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.
| Property | Type | Description |
|---|---|---|
name | string | Display name |
currency_id | string | Currency to grant |
event_type | string | Event type to match |
event_filter | object | Optional filter expression |
calculation | object | How to calculate amount |
max_per_event | integer | Cap per single event |
max_per_day | integer | Daily cap per user |
max_per_week | integer | Weekly cap per user |
priority | integer | Higher = evaluated first |
Loyalty Tiers
Tiers provide a progression system based on lifetime currency earnings:
| Property | Type | Description |
|---|---|---|
tier_name | string | Display name |
tier_level | integer | Numeric level (1, 2, 3...) |
min_lifetime_points | integer | Points required to reach tier |
currency_id | string | Currency to track for progression |
icon_url | string | Badge icon URL |
badge_color | string | Hex color code |
benefits | object | Tier-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:
| Parameter | Type | Description |
|---|---|---|
user_id | string | Required. User ID |
currency_id | string | Filter by currency (optional) |
limit | integer | Page size (default: 100, max: 1000) |
offset | integer | Page 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:
| Service | Integration | How it works |
|---|---|---|
| Streak | Automatic | Streak milestones with currency rewards call wallet.earn() |
| Coupon | Automatic | Coupon redemption with currency reward calls wallet.earn() |
| Leaderboard | Automatic | Leaderboard window completion grants rewards via wallet.earn() |
| Spin Wheel | Automatic | Wheel spin_cost deducts via wallet.spend(), currency prizes call wallet.earn() |
| Rewards Store | Automatic | Purchases deduct via wallet.spend() |
Webhooks
Configure webhooks to receive real-time wallet event notifications.
Event Types
| Event | Description |
|---|---|
currency.earned | Currency added to user balance |
currency.spent | Currency deducted from user balance |
tier.changed | User'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
- Currency Design: Start with a single primary currency, add more as needed
- Earning Rules: Set reasonable daily/weekly caps to prevent abuse
- Tier Progression: Make tiers achievable but meaningful
- Idempotency Keys: Always provide unique keys for grants/deducts to prevent duplicates
- Non-Spendable Currencies: Use for XP/reputation that shouldn't decrease
- Balance Caching: Cache balances client-side but refresh on critical actions
Next Steps
- Creating Leaderboards - Leaderboard integration