Cross-Game Transfers

Enable secure currency transfers between different titles in your ecosystem, whether you run a game or a platform. Players can seamlessly move their virtual currency across title boundaries with SMS verification and secure claim codes.

Overview

How Cross-Game Transfers Work

  1. Initiate Transfer: Source title initiates transfer with player verification and target recipient phone
  2. SMS Verification: Player receives SMS PIN to verify the transfer
  3. Claim Code Generation: System generates secure claim code after verification
  4. Phone-Secured Claim: Only the specified target phone number can redeem the claim code
  5. Completion: Funds are transferred and both titles receive fee distributions

🔒 BREAKING CHANGE: Phone-Based Security

Cross-game transfers now require target_player_phone during initiation. The claim code can ONLY be redeemed by this exact phone number.

  • • Transfer initiation now requires target_player_phone parameter
  • • Claim codes are locked to the specified phone number
  • • Phone mismatch during claim results in 403 Forbidden error
  • • This prevents unauthorized claim code sharing

Currency moves between games, not between games and platforms

The network has two segments. Games exchange currency with each other freely. That is what this API is for. Platform tenants (video, creator and social platforms) sit outside that segment: currency cannot move from a game to a platform tenant, or from a platform tenant into a game. Platform tenants still exchange value with one another.

  • • Moving currency from a game to a platform tenant returns 409 GAME_TO_PLATFORM_BLOCKED
  • • Moving currency from a platform tenant into a game returns 409 PLATFORM_TO_GAME_BLOCKED
  • • Destination lists never offer a tenant across the boundary, so a correctly built picker will not surface either error

Game-to-game transfers are unaffected in every respect. If your integration only involves games, this boundary never appears.

Steam-purchased currency transfers only to Steam titles

The rule is about currency moving between titles, not about what a player buys with it. Currency a player buys through Steam can be transferred 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.

  • Spending is never restricted by origin. A player can spend any balance on items in any title, Steam-distributed or not, including currency they bought through Steam. Item purchases never fail for this reason, so there is nothing to handle on the item path.
  • • Moving more than the non-Steam-origin balance to a non-Steam destination returns 409 STEAM_VALUE_NON_TRANSFERABLE
  • • Transferring from a non-Steam game into a Steam-distributed title returns 409 NON_STEAM_VALUE_INTO_STEAM_BLOCKED
  • • Destination lists expose accepts_steam_origin_value so you can gray out non-Steam destinations for Steam-purchased balances

✅ Key Benefits

  • • Secure SMS-based verification
  • • Automatic fee distribution
  • • Real-time balance updates
  • • Comprehensive fraud protection
  • • Support for different currencies

⚡ Fee Structure

  • Total Fee: 10% of transfer amount
  • Source Game: 3.5% fee share
  • Target Game: 3.5% fee share
  • Platform: 3% platform fee

Authentication

All transfer API endpoints authenticate with your title's secret key:

// Headers required for all requests
X-Game-Secret-Key: your_game_secret_key
Content-Type: application/json

Transfer Flow

1

Initiate Transfer

POST /api/transfers/initiate-transfer

Request Body:

{
  "client_request_id": "unique_transfer_id_123",
  "source_player_name": "John Doe",
  "source_player_email": "john@example.com",
  "source_player_phone": "+1234567890",
  "target_player_email": "jane@example.com",
  "target_player_phone": "+0987654321",
  "target_game_id": "456",
  "amount": "100.00"
}

Success Response:

{
  "status": "success",
  "message": "Transfer initiated. Please verify with SMS PIN.",
  "transaction_id": "TXN_1234567890",
  "order_id": "TFRO_1640995200_ABC123",
  "transfer_details": {
    "source_game": "Game A",
    "target_game": "Game B",
    "target_game_id": "456",
    "currency": "Gold Coins",
    "currency_id": 1,
    "amount_initiated": "100.00",
    "fees_preview": {
      "total_fee": "10.00",
      "source_game_fee": "3.50",
      "target_game_fee": "3.50",
      "invo_fee": "3.00",
      "net_amount": "90.00"
    }
  },
  "verification_required": {
    "phone_number_masked": "****7890",
    "pin_expires_in_minutes": 10
  }
}
2

Verify SMS PIN

POST /api/transfers/verify-sms

Request Body:

{
  "transaction_id": "TXN_1234567890",
  "sms_pin": "123456"
}

Success Response:

{
  "status": "success",
  "message": "SMS verification successful. Transfer pending claim.",
  "transaction_id": "TXN_1234567890",
  "claim_code": "KJMRS-47281",
  "new_balance": "500.00",
  "claim_instructions": {
    "message": "Provide this claim code to the intended receiver.",
    "target_game_id": "456",
    "target_game_name": "Game B",
    "claim_code_expires_at": "2024-01-02T10:00:00Z"
  },
  "transfer_summary": {
    "amount_initiated": "100.00",
    "net_amount_for_claim": "90.00",
    "fees_deducted": "10.00",
    "source_player_current_available_balance": "500.00"
  }
}

Guardian Approval Holds

When the transfer was initiated by a minor with a guardian on file, /verify-sms will not consume a PIN attempt and instead returns one of:

  • 202 GUARDIAN_APPROVAL_PENDING: held until the guardian approves via SMS
  • 410 GUARDIAN_APPROVAL_REJECTED / GUARDIAN_APPROVAL_EXPIRED: guardian rejected, or the approval window lapsed
  • 503 GUARDIAN_APPROVAL_CHECK_UNAVAILABLE: approval status could not be confirmed; retry shortly (no PIN attempt consumed)
3

Claim Transfer

POST /api/transfers/claim-transfer

Request Body:

{
  "claim_code": "KJMRS-47281",
  "target_player_name": "Jane Smith",
  "target_player_email": "jane@example.com",
  "target_player_phone": "+1987654321",
  "target_currency_id": "2"
}

Success Response:

{
  "status": "success",
  "message": "Transfer claimed successfully.",
  "transaction_id": "TXN_1234567890",
  "transfer_details": {
    "amount_received": "90.00",
    "source_game": "Game A",
    "target_currency": "Silver Coins",
    "target_player": "Jane Smith",
    "new_balance": "590.00"
  },
  "completion_time": "2024-01-01T12:30:00Z",
  "order_id": "TFRO_1640995200_ABC123"
}

Additional Endpoints

Get Transfer Status

GET /api/transfers/{transaction_id}/status

Check the current status of a transfer transaction.

Returns: Transaction status, amount details, and completion information

Get Transfer History

GET /api/transfers/transfer-history?player_email=user@example.com

Retrieve transfer history for a specific player.

Parameters: player_email (required), limit (optional), offset (optional)

Security Status

GET /api/transfers/security-status?player_email=user@example.com

Check security restrictions and rate limits for a player.

Returns: Blocking status, velocity limits, and security metrics

Security Features

🛡️ Fraud Protection

  • • SMS PIN verification (10-minute expiry)
  • • Claim code verification (24-hour expiry)
  • • Rate limiting per player and IP
  • • Velocity limits (hourly/daily)
  • • Duplicate transfer detection
  • • Circuit breaker protection

⚙️ Rate Limits

  • Initiate: 100/hour per player
  • SMS Verify: 40/hour per player
  • Claim: 30/hour per player
  • IP Limits: 150-200/hour
  • Transfer Velocity: 10/hour per player
  • Daily Amount: configurable per-title limit (default 1,000,000 currency units, adjustable by the platform)

Error Handling

Common Error Responses

400 - Bad Request

Missing required fields, invalid amounts, or malformed data

401 - Unauthorized

Invalid or missing secret key

404 - Not Found

Transaction, title, or player not found

409 - Conflict

Duplicate client_request_id or transfer already processed

429 - Too Many Requests

Rate limit exceeded, includes retry_after header

Best Practices

💡 Implementation Tips

  • • Always use unique client_request_id values to prevent duplicates
  • • Implement proper error handling for all API responses
  • • Store transaction_id for status checking and support
  • • Validate phone numbers before initiating transfers
  • • Handle rate limit responses gracefully with exponential backoff
  • • Monitor transfer completion rates and failure reasons

⚠️ Important Notes

  • • SMS PINs expire after 10 minutes
  • • Claim codes expire after 24 hours
  • • Expired transfers return funds to source player
  • • Maximum 3 attempts for SMS verification
  • • Maximum 5 attempts for claim code verification
  • • All timestamps are in UTC format

Testing in Sandbox

Use sandbox environment for testing transfers without real SMS or charges:

// Sandbox API Base URL
https://sandbox-api.invo.com
// Test SMS PIN (sandbox only)
SMS PIN: 123456
// Test phone numbers
+1555XXXXXXX (any valid format)