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.
Not in this SDK (by design): the player-token WebAuthn ceremonies — passkey enroll, approve/step-up, confirm-receipt/claim, the enrollment OTP grant, and device link. Those run in the browser (navigator.credentials) via the Web SDK'sInvoClient, or you relay them through your backend. Everything game-secret (mint, initiate, verify-SMS, claim, status, guardian, phone-share, checkout, purchase, item purchase, balances, inbound-pending, destinations, linked-identities, webhook verification) is here.
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
python3 -m pip install invonetworkWindows
py -m pip install invonetworkRecommended: inside a virtual environment
python3 -m venv .venv
source .venv/bin/activate
pip install invonetworkpy -m venv .venv
.venv\Scripts\Activate.ps1
pip install invonetworkFull stack? Install the server SDK here and the browser SDK on your web front-end:
# Python backend (this package)
pip install invonetwork
# Web / browser front-end (Node ≥ 18)
npm install @invonetwork/web-sdkQuickstart
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).
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.
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 "", 200from 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)Inbound "to collect" (incoming from other games)
get_inbound_pending lists a player's incoming, unclaimed sends/transfers — including value sent from other games to a player on your platform. It's the source of truth behind a "you have X to collect" badge, and pairs with the transfer.claim_pending webhook.
pending = server.get_inbound_pending(player_email="p@example.com") # or player_phone=...
for row in pending.inbound_pending:
# row.source_game = where it came from (another game). Match row.to_phone to the player;
# row.to_identity_id is None when the phone maps to >1 of your players (don't require it).
print(row.transaction_id, row.net_amount, row.to_phone, row.source_game)
# Lists only pending/unclaimed; a claimed transfer drops off.This is the server/platform view (game-secret). The browser player-token equivalent is the JS SDK's client.getPendingCollect(), where incoming rows come back as kind="receiving_confirm" — render those, not just player-initiated ones.
Errors
Every failure raises InvoError with .code, .status,.message, .body, and .request_id, plus classifier helpers so you don't string-match:
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:
raiseWhat's included
- Tokens & flows:
mint_player_token,initiate_send,initiate_transfer,create_checkout,purchase_currency,confirm_payment,purchase_item - Completion & recovery (v0.2.0+):
verify_sms_transfer/verify_sms_send,claim_transfer/claim_currency,get_transfer_status/get_send_status,get_guardian_approval_status - Phone-share & destinations (v0.3.0+):
phone_share_initiate/phone_share_approve/phone_share_status(resolve a409 PHONE_SHARE_APPROVAL_REQUIRED), and server-sideget_destinations(source_game_id=..., direction=...) - 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_webhookwith a typedWebhookEvent - Resilience: automatic retries (network / 429 / 5xx, idempotent-only, honoring
retry_after), timeouts, and observability hooks - Types: full type hints, ships
py.typed(mypy --strictclean)