Launch checklist and traps
What must be true before you create a real subscription, followed by the traps that account for nearly every support question we have seen. Each one links to the page that explains it.
Before launch
The sandbox clock tools are not under /api
https://sandbox.invo.network/sandbox/subscriptions/<id>/advance-clock, not .../sandbox/api/subscriptions/.... The wrong path is a plain 404 that looks like a missing feature. Every other subscription call is under /api.
Card traps
- Replay before retry. On any timeout or 5xx from
/subscribe, send the same body again. A newclient_request_idin a retry loop lands on409 ACTIVE_SUBSCRIPTION_EXISTSat best and a second subscription at worst. requires_actionis notpaid. Nothing has been charged. Do not grant paid access untilsubscription.renewed.pendingis notfailed. Wait for the event; Invo resolves it.current_period_endis not entitlement. Usepaid_through.payment_failedandpast_duedo not revoke access. Onlyexpired(atfinal_period_end) andcanceled(ataccess_until) do.authentication_requiredis not a failure. Do not send the member a “payment failed” message; send them the link.- Do not poll for renewals. Subscribe to
subscription.renewed. - One live subscription per item, on either rail. Cancel the old one before creating a new one for the same
item_id, or change the price with/amount. - At-period-end cancel on an unpaid subscription is immediate. Read
terminated_immediatelyon the response. - Price changes apply next period, and big jumps do not apply at all. More than double, or above
500.00, stays pending forever. - A card saved without off-session consent cannot back a subscription. Cards saved through
saved_card_idpurchases, through hosted checkout, or before the mandate change need to be saved again through/setup-intentor asave_card: truepurchase. wallet_onlyis absolute. A card attached later is ignored until you passwallet_only: falseon/payment-method.sub_is reserved as a prefix for every idempotency key on the platform, not only here.- Metadata is yours except
_invo. Invo writes cancellation reasons under that key. - Send
consent. It is what a disputed charge is argued from. - Do not store or log
confirmation_urlbeside anything you publish. It is a bearer link. subscription.period_seqis the next period, not the paid one. After a paid first charge it already reads2. Key entitlement onpaid_period_seqand on theperiod_seqofsubscription.renewed.
Steam traps
- Finalize is your call. Steam does not tell Invo the member authorised; your game must call
/steam/finalizeafter the overlay or checkout closes, and again if it returnednot_authorized. - Match the client callback on
steam_order_id, never onsteam_transid. Store it against thesubscription_idbefore you return from init. - 24 hours to authorise. An unfinalised subscription expires and must be re-created with a new key.
websessions needplayer_ipand the checkout URL must open top-level; it cannot be framed.- Only the primary key or the
steamchannel key may start a Steam subscription. - No trials, no
interval_countother than 1, nowallet_only, no card fields. - The price is a ceiling. Increases are refused; start a new Steam subscription. Decreases stage.
/payment-methodis refused on a Steam subscription.- Coins per period depend on the member’s country and are fixed at init. Show
amount_coinsfrom the init response, not price times 10. - An empty wallet pays the full price; a partial wallet pays only for the missing coins. Both are correct and both appear as
funding.steam_charged_usd. - A member can cancel from Steam. You learn it from
subscription.canceledwithcanceled_by: "steam"; access runs toaccess_until. - Some Steam renewals are deferred for reasons on your side (billing setup, credit, Steam configuration) with no event. Keep your Invo billing setup healthy and watch
next_charge_at. - Replaying
/steam/initreturns the row’s current status, which may already beactive,canceledorexpired. - A second init while one is pending reuses it. Same terms, same Steam account and same
usersession: you get the pending row back withpending_reuse: trueand the samesteam_order_id, and no second agreement is opened. Anything else:409 STEAM_AUTHORIZATION_PENDINGcarrying the pendingsubscription_id. - Reuse is deliberately narrow, and narrower is not the same as safer. It would be easy to read the rule as “reuse whenever the price matches, because a duplicate agreement is the danger”. It is not. An authorisation belongs to one Steam account and one session type: hand a member an agreement her current Steam account cannot see, or a web checkout link when she is in the overlay, and she is stuck for a full day with no way to act. A rival agreement she can answer is the lesser harm, so the rule fails toward “she can act” rather than toward “nothing is duplicated”. When you get the 409, show her the pending one and let her finalize or cancel it.
- Same
item_idas the card version of the membership.409 ACTIVE_SUBSCRIPTION_EXISTSacross rails is the guard against double-billing, not a bug. - Refunds of Steam-charged periods are not supported yet.
/refundanswers409 STEAM_REFUND_NOT_SUPPORTED; wallet-covered periods refund in coins. - Sandbox does not exercise Valve.
sandbox_auto_approved: truemeans the charge was simulated; do one real Steam sandbox authorisation before launch. split.total_usdon Steam is not the price. It is what the coins are worth after Valve’s share and VAT; the price is the top-levelamount_usd.- Two expiries arrive with no
payment_failedbefore them:steam_authorization_abandonedandsteam_containment. Handlesubscription.expiredon its own.
Every code behind these traps is on the error reference.