Steam Payments
If your game is on Steam, players can top up your branded in-game currency without ever leaving the Steam client. They pay with their Steam Wallet — or any card, PayPal, or regional method already linked to their Steam account — and approve the charge in Steam's own purchase overlay. No checkout page, no card form, no redirect: the payment happens in the storefront the player already trusts, and INVO credits their balance the instant the charge is captured.
Zero Steam-side setup
Every INVO game distributed on Steam shares one INVO Steam application for currency purchases. You do not register your own Steam app for this, you do not request or store a Steam publisher key, and there is no per-game Steam configuration. INVO operates the Steam application, holds the publisher credentials server-side, and reconciles settlement. Your integration is just three HTTP calls and one Steam SDK callback.
a Steam partner account for payments
a Steam publisher Web API key
any per-game Steam setup in INVO
How it works
Steam splits a micro-transaction into two phases. First the player authorizes the charge in the Steam client. Then the merchant captures it. INVO drives both phases and credits the player's branded currency the moment the charge is captured — the same credit-on-capture model as card purchases.
Three parties are involved: your game (client + backend), INVO, and Steam. Your game backend makes two server-to-server calls to INVO; your game client handles one Steam SDK callback in between. The handshake:
Initialize
Your backend calls /steam/init-purchase. INVO creates the order and opens a transaction on Steam. Steam then shows the purchase overlay to the player.
Authorize
The player approves the charge in Steam. Your game client receives the MicroTxnAuthorizationResponse_t Steam SDK callback.
Finalize
Your backend calls /steam/finalize-purchase. INVO verifies the authorization with Steam, captures the charge, and credits the player.
Why two server calls? Your X-Game-Secret-Key authenticates the INVO API and must never ship inside a game client. So the game client never calls INVO directly for payments — it calls your backend, and your backend calls INVO. The only thing the client touches is the Steam SDK callback.
Step 1 — Initialize the purchase (backend)
When the player picks a currency pack, your backend calls INVO. You pass the player's 64-bit steamid — your game client already has it from the Steamworks SDK and forwards it to your backend — and the USD amount.
POST https://invo.network/api/currency-purchases/steam/init-purchase
Headers:
X-Game-Secret-Key: ivsdk_<your_sdk_key>
Content-Type: application/json
Body:
{
"player_email": "player@example.com",
"steamid": "76561198000000000",
"usd_amount": "10.00",
"purchase_reference": "<uuid_v4>",
"player_name": "PlayerOne"
}
Response 200:
{
"status": "pending_authorization",
"order_id": "ord_a1b2c3...",
"steam_transid": "350000123456789",
"new_balance": null
}player_email— identifies the INVO player whose branded-currency balance is credited. A player row is created on first purchase if it doesn't exist.steamid— the 64-bit Steam ID of the account paying. Steam shows the authorization overlay to this account.usd_amount— the charge in USD, as a string. Maximum999.99per transaction.purchase_reference— your idempotency key. Replaying the same value returns the existing order (409) instead of creating a second charge — safe to retry on a network failure.order_id(response) — keep this. Your game client passes it back to your backend for Step 3.
As soon as init-purchase succeeds, Steam presents its purchase dialog to the player inside the Steam overlay. You do not trigger any UI yourself.
Step 2 — The player authorizes (game client)
Steam delivers the player's decision to your game client through the Steamworks SDK callback MicroTxnAuthorizationResponse_t. The callback carries m_bAuthorized (1 if the player approved), m_unAppID, and m_ulOrderID — the numeric order id, useful if you have several purchases in flight at once. When the player approves, tell your backend to run Step 3. INVO never sees this callback; it is strictly between Steam and your game client.
Unity (C#, Steamworks.NET)
using Steamworks;
using UnityEngine;
public class InvoSteamPurchase : MonoBehaviour
{
private Callback<MicroTxnAuthorizationResponse_t> _authCallback;
private string _pendingOrderId;
void Start()
{
// Register the Steam authorization callback once.
_authCallback = Callback<MicroTxnAuthorizationResponse_t>.Create(OnSteamAuthorized);
}
// Called when the player taps "Buy" on a currency pack.
public async void BuyCurrency(string usdAmount)
{
ulong steamId = SteamUser.GetSteamID().m_SteamID;
// Ask YOUR backend to start the purchase. Your backend calls INVO's
// /steam/init-purchase with your X-Game-Secret-Key (never in the client).
var result = await MyGameBackend.StartSteamPurchase(usdAmount, steamId);
_pendingOrderId = result.OrderId; // INVO's "ord_..." id
// Steam now shows the purchase overlay to the player automatically.
}
// Steam fires this when the player approves or declines.
private void OnSteamAuthorized(MicroTxnAuthorizationResponse_t cb)
{
if (cb.m_bAuthorized == 1)
{
// Approved — ask your backend to finalize (it calls INVO).
MyGameBackend.FinalizeSteamPurchase(_pendingOrderId);
}
else
{
// Declined or cancelled — nothing was charged.
}
}
void OnDestroy() => _authCallback?.Dispose();
}Unreal Engine (C++, Steamworks SDK)
#include "steam/steam_api.h"
class FInvoSteamPurchase
{
public:
// Called when the player taps "Buy" on a currency pack.
void BuyCurrency(const FString& UsdAmount)
{
const uint64 SteamId = SteamUser()->GetSteamID().ConvertToUint64();
// Ask YOUR backend to start the purchase. Your backend calls INVO's
// /steam/init-purchase with your X-Game-Secret-Key (never in the client).
MyGameBackend->StartSteamPurchase(UsdAmount, SteamId,
[this](const FString& OrderId)
{
PendingOrderId = OrderId; // INVO's "ord_..." id
// Steam shows the purchase overlay to the player automatically.
});
}
private:
FString PendingOrderId;
// Steam fires this when the player approves or declines.
STEAM_CALLBACK(FInvoSteamPurchase, OnSteamAuthorized,
MicroTxnAuthorizationResponse_t);
};
void FInvoSteamPurchase::OnSteamAuthorized(MicroTxnAuthorizationResponse_t* cb)
{
if (cb->m_bAuthorized)
{
// Approved — ask your backend to finalize (it calls INVO).
MyGameBackend->FinalizeSteamPurchase(PendingOrderId);
}
// else: declined or cancelled — nothing was charged.
}The callback only reports the player's intent — it is not proof of payment. INVO independently re-verifies the authorization with Steam in Step 3 before any money moves or any currency is credited.
Step 3 — Finalize the purchase (backend)
After your client reports an authorized callback, your backend calls INVO with the order_id from Step 1. INVO verifies the authorization with Steam server-side, captures the charge, and credits the player's branded currency in one atomic step.
POST https://invo.network/api/currency-purchases/steam/finalize-purchase
Headers:
X-Game-Secret-Key: ivsdk_<your_sdk_key>
Content-Type: application/json
Body:
{ "order_id": "ord_a1b2c3..." }
Response 200:
{
"status": "success",
"transaction_id": "txn_...",
"order_id": "ord_a1b2c3...",
"new_balance": "100",
"already_processed": false
}finalize-purchase is idempotent — a retry, or a duplicate call, returns already_processed: true and never credits twice. If the player has not finished authorizing yet, it returns 409 with status: "not_authorized" — wait for the Steam callback and call again.
Responses & error handling
| HTTP | status | What it means / what to do |
|---|---|---|
| 200 | success | Purchase captured and currency credited. Read new_balance. |
| 200 | pending_authorization | init-purchase succeeded; the player must now authorize in Steam. |
| 409 | not_authorized | finalize was called before the player approved. Wait for the Steam callback, then retry. |
| 409 | duplicate | This purchase_reference already has an order. Use the returned order_id. |
| 502 | steam_error | Steam rejected or could not process the call. Safe to retry. |
| 503 | service_unavailable | Steam is temporarily unreachable (circuit breaker open). Retry shortly. |
Even if your backend never gets to call finalize-purchase — a crash, a dropped connection — INVO's reconciliation poller independently detects the authorized Steam transaction and credits the player. A missed finalize call delays the credit; it never loses it.
Refunds
Steam refunds are reconciled automatically — you do not handle them. When a Steam purchase is refunded, INVO debits (claws back) the branded currency it originally credited, exactly as it does for a card refund, and emits the purchase.refunded webhook to your tenant.
The clawback is allowed to drive a balance negative if the player has already spent the currency — the negative balance is settled against the player's next top-up. This keeps your ledger honest without blocking the refund.
Sandbox testing
In the INVO sandbox environment the Steam rail runs against Steam's ISteamMicroTxnSandbox interface — the identical init / authorize / finalize handshake against Steam's test system, with no real money charged. The endpoint paths, request bodies, and responses are exactly the same as production, so the integration you build in sandbox ships unchanged.
Run the full three-step flow there — including a declined authorization and a refund — before going live. See Sandbox Testing.
Good to know
Server-side only
Both /steam/* endpoints require your X-Game-Secret-Key and must be called from your backend. The game client only ever handles the Steam SDK callback.
Credit on capture
The player's branded currency is credited the instant finalize-purchase captures the charge — no waiting on Steam's multi-day settlement cycle.
Reconciliation safety net
If your backend ever misses the finalize call, INVO's poller detects the authorized transaction and credits the player anyway. Refunds are caught the same way.
One balance, every rail
Steam purchases credit the same branded-currency balance as card purchases and cross-game transfers. To your players, the payment rail is invisible.
Idempotent by design
purchase_reference on init and the order_id on finalize make every call safe to retry — no double charges, no double credits.
Authorization re-verified
A client callback can be spoofed; INVO doesn't trust it. Every finalize independently re-checks the authorization with Steam before capturing.