Python SDK (server)

invonetwork is the official first-party server-side SDK for integrating Invo from a Python backend (Django, Flask, FastAPI, or plain Python). It's the Python counterpart to the Web / TypeScript SDK — same endpoints, field names, and webhook signing scheme, so both stay in lockstep.

Server-side only — holds your game secret

This SDK authenticates with your game secret and is meant for your backend. For the browser half (passkey enroll/approve, self-claim, balances/destinations for a logged-in player), pair it with the Web SDK — the game secret never reaches the browser.

Install and call in minutes

pip install invonetwork — Python 3.9+, zero runtime dependencies. View on PyPI and GitHub.

Currency & Item Purchase

Hosted checkout, direct rail, and spend-currency item purchases

Sends & Transfers

Initiate on the server; the browser approves by passkey

Balances & Reads

Player balances, inbound-pending, linked identities, order details

Webhook Verification

Constant-time HMAC verification with typed events

Install

Requires Python 3.9+. The exact command differs slightly by OS — use the one for your platform:

macOS / Linux

Terminal (macOS / Linux)
python3 -m pip install invonetwork

Windows

PowerShell (Windows)
py -m pip install invonetwork

Recommended: inside a virtual environment

macOS / Linux
python3 -m venv .venv
source .venv/bin/activate
pip install invonetwork
Windows (PowerShell)
py -m venv .venv
.venv\Scripts\Activate.ps1
pip install invonetwork

Full stack? Install the server SDK here and the browser SDK on your web front-end:

The two SDKs
# Python backend (this package)
pip install invonetwork

# Web / browser front-end (Node ≥ 18)
npm install @invonetwork/web-sdk

Quickstart

Construct one InvoServer with your game secret and reuse it. Get your secret and base URLs from the Invo console (https://console.invo.network for production, https://dev.console.invo.network for testing).

server.py
from invonetwork import InvoServer

invo = InvoServer(
    game_secret="sk_live_...",                       # server-side ONLY
    base_url="https://sandbox.invo.network/sandbox", # prod: https://invo.network
)

# Mint a short-lived, game-scoped token for the browser SDK to use
token = invo.mint_player_token(player_email="player@example.com")

# Spend game currency on an in-game item (balance debit; grant off the item.purchased webhook)
order = invo.purchase_item(
    client_request_id="req-123",                     # idempotency key
    player_email="player@example.com",
    player_name="Ada",
    item_id="sword_001",
    item_name="Flaming Sword",
    item_quantity=1,
    unit_price="150.00",
    total_price="150.00",
)

# Read a player's balances
balance = invo.get_player_balance(player_email="player@example.com")

Verify webhooks

Pass the raw request bytes (never a re-serialized object) and your signing secret. verify_webhook checks the HMAC in constant time, rejects stale timestamps, supports secret rotation, and returns a typed event.

Flask
from flask import Flask, request
from invonetwork import verify_webhook, InvoError

app = Flask(__name__)

@app.post("/invo/webhooks")
def invo_webhook():
    try:
        event = verify_webhook(
            request.get_data(),                       # RAW bytes
            request.headers.get("X-Invo-Signature"),
            "whsec_...",                              # your signing secret (or a list during rotation)
        )
    except InvoError:
        return "", 400                               # bad signature / stale / malformed

    if event.event_type == "purchase.completed":
        grant_currency(event.data)                    # idempotent on event.idempotency_key
    return "", 200
FastAPI
from fastapi import FastAPI, Request, Response
from invonetwork import verify_webhook, InvoError

app = FastAPI()

@app.post("/invo/webhooks")
async def invo_webhook(request: Request):
    raw = await request.body()
    try:
        event = verify_webhook(raw, request.headers.get("X-Invo-Signature"), "whsec_...")
    except InvoError:
        return Response(status_code=400)
    # handle event.event_type ...
    return Response(status_code=200)

Errors

Every failure raises InvoError with .code, .status,.message, .body, and .request_id, plus classifier helpers so you don't string-match:

error handling
from invonetwork import InvoError

try:
    invo.purchase_item(...)
except InvoError as e:
    if e.is_insufficient_balance:
        ...           # not enough currency
    elif e.is_duplicate_request:
        ...           # replayed client_request_id
    elif e.retry_after is not None:
        ...           # throttled; back off e.retry_after seconds
    else:
        raise

What's included

  • Tokens & flows: mint_player_token, initiate_send, initiate_transfer, create_checkout, purchase_currency, confirm_payment, purchase_item
  • Reads: get_order_details, get_item_purchase_history (+ iterate_item_purchase_history), get_item_order_details, get_player_balance, get_inbound_pending, get_linked_identities
  • Webhooks: verify_webhook with a typed WebhookEvent
  • Resilience: automatic retries (network / 429 / 5xx, idempotent-only, honoring retry_after), timeouts, and observability hooks
  • Types: full type hints, ships py.typed (mypy --strict clean)