Wallet & Currency API
Phoenix provides a comprehensive multi-currency wallet system for managing virtual currencies, loyalty points, and in-app balances.
Wallet & Currency API
Phoenix provides a comprehensive multi-currency wallet system for managing virtual currencies, loyalty points, and in-app balances.
Overview
The Wallet system provides:
- 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
- Webhooks: Real-time notifications for wallet events
Client Endpoints
All client endpoints require JWT or HMAC authentication.
Get User Balances
Retrieve all currency balances for a user. Tenant and default user are from the JWT; optional user_id query param.
GET /v1/wallet/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/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/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
}Note: Returns null if no tier is configured or user hasn't earned any points.
JavaScript Example
class WalletClient {
constructor(apiUrl, jwt) {
this.apiUrl = apiUrl;
this.jwt = jwt;
}
async getBalances(userId) {
const response = await fetch(
`${this.apiUrl}/v1/wallet/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(userId, options = {}) {
const params = new URLSearchParams({
user_id: userId,
...options
});
const response = await fetch(
`${this.apiUrl}/v1/wallet/transactions?${params}`,
{
headers: {
'Authorization': `Bearer ${this.jwt}`,
'Content-Type': 'application/json'
}
}
);
return response.json();
}
async getTier(userId) {
const response = await fetch(
`${this.apiUrl}/v1/wallet/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 (tenant from JWT)
const { balances } = await wallet.getBalances('user_123');
console.log(`Gold Coins: ${balances.find(b => b.currency_id === 'gold_coins')?.available || 0}`);
// Get tier info
const tier = await wallet.getTier('user_123');
if (tier) {
console.log(`Current tier: ${tier.tier_name} (${tier.points_to_next} points to ${tier.next_tier_name})`);
}Currency Configuration
Currency Properties
Each currency has the following properties:
| 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
}
]Earning Rules
Earning rules automatically grant currency when specific events occur.
Rule Configuration
| 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 |
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"
}Loyalty Tiers
Tiers provide progression based on lifetime earnings.
Tier Configuration
| Property | Type | Description |
|---|---|---|
tier_name | string | Display name |
tier_level | integer | Numeric level (1, 2, 3...) |
min_lifetime_points | integer | Points required |
currency_id | string | Currency to track |
icon_url | string | Badge icon URL |
badge_color | string | Hex color code |
benefits | object | Tier-specific benefits |
Integration with Other Services
The Wallet automatically integrates with other Phoenix gamification services:
| Service | Integration | How it works |
|---|---|---|
| Streak | Automatic | Streak milestones with reward_item_id (currency type) 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() |
Admin Endpoints
Admins manage currencies, rules, and tiers via the Admin API.
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}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}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/deductLoyalty 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}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 Security
All webhooks include an X-Webhook-Signature header with an HMAC-SHA256 signature of the payload using your configured secret.
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
- Testing: Use the admin grant endpoint to simulate earnings during development