Player-to-Player Currency Sends Overview

Enable secure, verified currency sends directly between players, either within the same game or across different games. Our send system uses SMS verification and claim codes to ensure only authorized sends are completed.

Steam-purchased currency stays within Steam titles

Currency a player buys through Steam can move among Steam-distributed games but not to non-Steam games, and non-Steam currency can't move into Steam titles. This containment is configured per-title by the platform and does not affect games that aren't Steam-distributed. When it applies, non-Steam destinations are omitted from destination lists for Steam-purchased balances, and an over-limit or wrong-direction send is rejected with a STEAM_VALUE_NON_TRANSFERABLE or NON_STEAM_VALUE_INTO_STEAM_BLOCKED 409.

Integration Flow for Game Developers

Step 1: Get Available Destinations (Cache Daily)

Before building your currency send UI, fetch the list of games your players can send currency to. This data should be cached locally and refreshed once per day.

POST /api/currency-sends/available-destinations

Cache Duration: 24 hours (games don't change frequently)

Refresh Trigger: Daily app startup or manual refresh

Storage: Local file, localStorage, or in-memory cache

Performance: Prevents API calls every time player opens send UI

2

Build Currency Send UI

Use the cached destination data to create your send interface. Show game names, icons, currency types, receiver phone input, and amount validation.

UI Elements: Game selection dropdown, phone number input, currency display, amount input with min/max validation, fee preview.

3

Process Currency Send

When player confirms send, call the initiate → verify → claim sequence using the currency send APIs.

API Sequence: /initiate-send → /verify-sms → /claim-currency (receiver's game)

Note: handle HTTP 202. If the sending account is flagged as a minor on Invo, /initiate-send returns 202 pending_guardian_approval with a polling endpoint. Adult accounts are unaffected. See Guardian Approval.

Send Process

4 Steps

Get Destinations → Initiate → Verify → Claim

Security

SMS + PIN

Two-factor verification

Send Fee

10%

Platform + game fees

Cache Duration

24 Hours

Destinations API cache

How Player-to-Player Currency Sends Work

0

Get Available Destinations (Daily)

Before showing send options, your game calls the destinations API to get the current list of games players can send currency to. This data is cached locally for 24 hours for performance.

What happens: API returns game list based on universal_transfers setting, data cached locally, send UI populated.

1

Initiate Currency Send

Player selects destination game from cached list, enters receiver's phone number, amount, and confirms send. The system validates the send, calculates fees, and reserves the amount from their balance. 🔒 The claim code is locked to the receiver's phone.

What happens: Amount is reserved, claim code locked to receiver phone, SMS PIN generated and sent to sender, send enters "pending verification" status.

2

SMS Verification

Sender receives an SMS with a verification PIN (valid for 10 minutes). They enter this PIN in their game to confirm they authorized the send. This prevents unauthorized currency sends.

What happens: PIN is verified, claim code is generated and sent to receiver via SMS, send enters "pending claim" status.

3

Claim Sent Currency

Receiver uses the claim code in their game to receive the sent currency. 🔒 The claim code can ONLY be redeemed by the exact phone number specified during initiation. Valid for 24 hours, one-time use only.

What happens: Phone number verified, currency credited to receiver's balance, send completed, confirmation SMS sent.

Types of Currency Sends

Same-Game Sends

Send currency to another player within the same game. Perfect for peer-to-peer transactions, gifting, or marketplace purchases between players.

  • • Instant delivery after verification
  • • Same currency type maintained
  • • Ideal for player-to-player trading
  • • Community building features

Cross-Game Sends

Send currency to a player in a different game within your ecosystem. Enables value transfer across your entire game portfolio.

  • • Cross-game value portability
  • • Ecosystem connectivity
  • • Player retention across games
  • • Unified economy benefits

Send Mode Logic

Universal Sends

When your game has universal_transfers = "yes"

  • • Players can send to ALL live games
  • • Includes same-game sends (peer-to-peer)
  • • Maximum ecosystem connectivity
  • • Excludes testing/inactive games

Linked Sends

When your game has universal_transfers = "no"

  • • Players can send ONLY to linked games + same game
  • • Uses your game's linked_game_ids field
  • • Controlled partnerships
  • • Curated send destinations

Caching Implementation Best Practices

Why Cache the Destinations API?

  • Performance: Instant send UI loading
  • Reliability: Works even if API is temporarily unavailable
  • Cost: Reduces API calls (game list doesn't change often)
  • User Experience: No loading delays when opening send screen

Implementation Guidelines

  • • Call /available-destinations once per day and cache locally
  • • Use cached data to build send UI instantly
  • • Refresh cache on app startup or manual refresh
  • • Store in persistent storage (not just memory)
  • • Handle cache miss gracefully with loading states
  • • Show game icons and names from destinations data
  • • Validate send amounts against min/max limits from response

Fee Structure

Default split (both tenants at 3.5%)

Sending tenant fee3.5%
Receiving tenant fee3.5%
Network fee (Invo)3.0%
Total user-paid fee10.0%

Example send

Send amount:1,000 Gold
Total fees (10%):-100 Gold

Receiver gets:900 Gold

Per-tenant rate overrides

Same policy as transfers: each tenant has a transfer_fee_percentage field (default 3.5%, range 0–10) that controls its kickback share on every send it participates in. Negotiated rates above 3.5% absorb from Invo's 3% slice; the user-paid total stays at exactly 10%. Read fee_breakdown from the API response or fee_total from the webhook for the actual numbers — don't recompute client-side.

Security & Anti-Fraud Features

🔒 Phone-Based Claim Security

Currency send claim codes are now locked to specific phone numbers. Only the receiver phone specified during initiation can redeem the claim code.

  • receiver_player_phone required at initiation
  • • Claim codes can ONLY be redeemed by the specified phone number
  • • Phone mismatch results in 403 Forbidden error
  • • Prevents unauthorized claim code sharing and redemption

SMS Verification

  • • SMS PIN required to authorize sends
  • • 10-minute expiration window
  • • Maximum 3 attempts per PIN
  • • Rate limiting on SMS requests
  • Phone-locked claim codes

Fraud Prevention

  • • Send velocity limits per player
  • • Duplicate send detection
  • • IP-based rate limiting
  • • Failed attempt tracking and blocking

Time Limits

  • • SMS PIN: 10 minutes to verify
  • • Claim code: 24 hours to claim
  • • Expired sends return funds automatically
  • • No funds lost due to expiration

Player Protection

  • • Funds reserved during verification
  • • Automatic refunds on failure/expiration
  • • Clear status tracking throughout process
  • • SMS notifications at each step

Rate Limits & Restrictions

Current Limits (Launch Period)

Destinations API

  • • 100 requests per minute per IP
  • • Should be cached for 24 hours
  • • Call once daily at app startup

Per Player Limits

  • • 100 send initiations per hour
  • • 35 SMS verifications per hour
  • • 25 claim attempts per hour
  • • 25 sends per hour (velocity limit)
  • • 1,000,000 currency units per day (default, configurable)

Per IP Address Limits

  • • 300 initiations per hour
  • • 175 verifications per hour
  • • 125 claims per hour
  • • 20 requests per minute (initiate-send burst limit)

Note: These limits are generous during the initial launch period and may be adjusted based on usage patterns and security requirements.

Currency Sends vs Cross-Game Transfers

Currency Sends

  • Player-to-player: Direct recipient via phone number
  • Same or cross-game: Can send within game or to other games
  • Immediate notification: Claim code sent directly to receiver
  • Personal transactions: Perfect for gifting and player trades
  • Phone-based claiming: Receiver identified by phone number

Cross-Game Transfers

  • Self-transfer: Moving your own currency between games
  • Cross-game only: Must be between different games
  • Manual claim sharing: Sender shares claim code manually
  • Portfolio management: Managing currency across your games
  • Game-based claiming: Claim in specific target game

Send Status Definitions

pending_pin_verification

Currency send initiated, waiting for SMS PIN verification from sender. Funds are reserved.

pending_claim

SMS verified, claim code sent to receiver. Waiting for recipient to claim the currency.

completed

Currency send successfully claimed and completed. Currency credited to receiver.

failed / expired

Send failed verification, expired, or was cancelled. Funds returned to sender.

Polling Send Status

GET

/api/currency-sends/<transaction_id>/status

Retrieve the current state of a currency send

Authentication & Access

Include a game secret key in the request headers. Accessible by either the sending game or the receiving game on the send:

X-Game-Secret-Key: your_game_secret_key_here

Rate Limit

60 requests per minute per IP address. Suitable for light polling while a send is in flight.

Use this to track a send through its lifecycle (e.g., after initiate, while waiting for the receiver to claim). The response shape varies with the current status — verification, claim, completion, and failure details are returned conditionally.

Example Response (200 OK)

{
  "status": "success",
  "transaction_id": "txn_send123abc456",
  "transaction_type": "currency_send",
  "transaction_status": "pending_claim",
  "verification_state": "approved",
  "amount": "500.00",
  "net_amount": "450.00",
  "fee_amount": "50.00",
  "created_at": "2024-06-24T15:00:00Z",
  "sending_game_id": "123456789012",
  "sending_game_name": "Adventure Quest",
  "receiving_game_id": "987654321098",
  "receiving_game_name": "Space Warriors",
  "currency_id": 1,
  "currency_name": "Gold",
  "order_id": "ord_send789def012",

  // Conditional — depends on transaction_status:
  // pending_pin_verification / pending_verification:
  "verification_info": { "sms_pin_expires_at": "2024-06-24T15:10:00Z" },
  // pending_claim / verified:
  "claim_info": { "claim_code_expires_at": "2024-06-25T15:00:00Z" },
  // completed:
  "completed_at": "2024-06-24T15:30:00Z",
  // present only when the send failed:
  "failure_reason": "..."
}
FieldDescription
transaction_statusCurrent send state (see Status Definitions above).
verification_state'awaiting' | 'approved' | 'completed' | 'expired' | 'failed'. Flips awaiting → approved when the sender clears verification (SMS or in-app); the field a waiting panel polls on.
amount / net_amount / fee_amountGross send amount, amount creditable on claim (after fees), and total fee. Decimal strings; may be null if not yet set.
sending_game_id / sending_game_nameThe originating game.
receiving_game_id / receiving_game_nameThe destination game.
currency_id / currency_nameCurrency being sent.
verification_infoPresent while awaiting SMS PIN verification — contains sms_pin_expires_at.
claim_infoPresent while awaiting claim — contains claim_code_expires_at.
completed_atPresent once the send is completed.
failure_reasonPresent only when the send failed.