Cross-Game Transfers

Enable secure currency transfers between different titles in your ecosystem, whether you run a game or a platform. Players move their virtual currency across title boundaries and approve each transfer with a passkey or a QR device approval.

Overview

How Cross-Game Transfers Work

  1. Initiate Transfer: Source title initiates transfer with player verification and target recipient phone
  2. Approval: The player approves with a passkey or a QR device approval, and your side calls the approve endpoint
  3. Collect at the destination: the transfer waits in the player's pending list in the destination title (GET /api/sdk/transfers/pending), and the player collects it there with POST /api/sdk/transfers/{id}/confirm-receipt, using a passkey or a QR device approval
  4. Completion: Funds are credited and both titles receive their fee shares. A transfer nobody collects returns to the player when the claim window closes

The player's phone is required

Cross-game transfers require target_player_phone at initiation. The destination matches the transfer to the player by that phone.

  • • Transfer initiation now requires target_player_phone parameter
  • • Collecting matches the player's current phone at the destination against the addressed number
  • • The claim code is a legacy fallback being retired, not the collection path; see Claim Code (fallback)

Currency moves between games and platforms

Currency moves freely across the network. Games and platform tenants (video, creator and social platforms) exchange currency with each other in both directions, through transfers and sends, exactly as two games do. Destination lists include both kinds of tenant.

  • • 409 GAME_TO_PLATFORM_BLOCKED and 409 PLATFORM_TO_GAME_BLOCKED are returned only if INVO restricts movement between games and platforms for the network. Nothing is reserved when either fires.

Payment routes still differ by type: platform tenants buy currency through the card route only. See Platform Partners.

Currency cannot move into a platform store title from outside it

The rule is about currency moving between titles, not about what a player buys with it. A title distributed through a platform store (a per-title setting INVO manages) cannot receive currency transferred or sent from a title outside that store. Currency bought through a platform store is subject to that store's rules and can only move between titles on that store. Titles that are not store-distributed are not affected.

  • • Transferring or sending from a title outside the store into a store-distributed title returns 409 NON_STEAM_VALUE_INTO_STEAM_BLOCKED
  • • Moving currency bought through a platform store to a title outside that store returns 409 STEAM_VALUE_NON_TRANSFERABLE: currency bought through a platform store can only move between titles on that store. Handle it.
  • • Destination lists already leave out store-distributed titles a non-store title cannot reach, so a picker built from them will not surface this error. Each destination's accepts_steam_origin_value says whether it accepts store-bought currency; read it rather than assuming

✅ Key Benefits

  • • Passkey or QR device approval
  • • 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. Approve on your enrolled device to continue.",
  "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"
    }
  }
}
2

Approve the transfer

POST /api/sdk/transfers/{id}/approve

The player proves who they are with a passkey (web and mobile) or a QR device approval (consoles, TVs and desktop builds), and your side calls the approve endpoint with that proof. The full request, every factor and every answer are on Cross-Game Transfer, stage by stage.

Success Response:

{
  "status": "approved",
  "next": "pending_claim",
  "transaction_id": "TXN_1234567890",
  "claim_code": "KJMRS-47281",
  "claim_code_expires_at": "2024-01-02T10:00:00Z",
  "claim_delivery": "email"
}

claim_delivery says how the claim code also reached the sender: email (mailed to the sender’s own bound address), none (nothing sent; use the code in this response), or sms (a legacy value). The same code is also returned to the sending game by GET /api/transfers/{id}/status once approved.

Guardian Approval Holds

When the transfer was initiated by a minor with a guardian on file, the approve endpoint instead returns one of:

  • • 202 GUARDIAN_APPROVAL_PENDING: held until the guardian approves; the request goes by email where one is verified
  • • 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
3

Collect at the destination

The destination title shows the transfer from the player's pending list and collects it with POST /api/sdk/transfers/{id}/confirm-receipt (passkey or QR device approval). Every call and answer is on Collecting, stage by stage. The claim-code route below is a legacy fallback being retired, kept for a player with no account at the destination; do not build new screens on it.

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

  • • Passkey or QR device approval on every transfer
  • • Rate limiting per player and IP
  • • Velocity limits (hourly/daily)
  • • Duplicate transfer detection
  • • Circuit breaker protection

⚙️ Rate Limits

  • • Request limits: per endpoint, per player and per title; see Rate Limits
  • • On a 429: wait the Retry-After seconds
  • • Daily Amount: one platform-wide limit per player per day, counted across all titles and set by INVO (not per title)

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

  • • A transfer nobody collects before the claim window closes returns to the source player
  • • All timestamps are in UTC format

Testing in Sandbox

Use the sandbox environment for testing transfers without real charges:

// Sandbox API Base URL
https://sandbox.invo.network/sandbox/api
// Test phone numbers
+1555XXXXXXX (any valid format)