FAQ / Currency and Payments
Currency and Payments: Questions and Answers
Your title's currency, payment methods, coin purchases, the platform store rail, item purchases and Platform Commerce. 102 questions, grouped by the documentation page each one is about. Where a response or webhook carries a figure, read it there rather than recomputing it.
Virtual Currency Setup
Read the Virtual Currency Setup page
Is the 1 USD = 10 units rate configurable, and why is it the same across the network?
Every currency uses the network rate of 10 units per $1 by default, which is what makes 100 units in one title worth the same as 100 units in another and keeps cross-game transfers at a known value. You cannot change it yourself. INVO can set a different rate for a currency, but only before that currency has any balances, transactions, orders or subscriptions; once it has activity the rate is locked.
How many units does $1 buy, and what is a 500-unit item worth in USD?
At the default rate, $1 buys 10 units, so a 500-unit item is worth $50.00. If INVO has set a different rate for your currency, that rate applies instead.
Which fields do I set for the default currency, and can I edit Currency Type?
You set currency_name, currency_symbol (up to 10 characters), an optional currency_symbol_url, transferable, minimum_transfer_amount and maximum_transfer_amount. currency_type is set to "default currency" when the title is created. You can later change it to premium, basic or special in the console. It is a label only and changes nothing about how the currency behaves.
Is the starting amount free currency that every new player receives?
No. The currency_amount field is a legacy setting that no balance or money path reads, so a new player starts with a balance of zero. If you want new players to have a starting grant, add it from your own game logic.
What happens if I turn Transferable off, and can I turn it back on later?
With transferable off, your currency is kept out of cross-game transfers and wallet moves, so it stays inside your title. It is an editable setting, so you can turn it back on later in the console (PUT /games/update/{game_id}).
How do the Conservative, Moderate, Liberal and Isolated transfer limit presets differ?
These are example configurations, not built-in presets. Conservative is 50 to 1,000 units per transfer, Moderate is 10 to 10,000, Liberal is 1 to 100,000, and Isolated turns transferable off. You enter whatever minimum_transfer_amount and maximum_transfer_amount suit your economy. Leaving the maximum empty means no per-transfer maximum.
What are the rules for the currency symbol image (format, size, 2 MB maximum)?
Upload a PNG, JPG or SVG of 2 MB or less, ideally a 64x64 or 128x128 PNG with a transparent background. The console uploads it and fills in currency_symbol_url for you, and currency_symbol_url accepts up to 500 characters if you host the image yourself.
Can one title have several currencies, and does the rate apply to all of them?
Not today. Each title has exactly one currency, and the rate (10 per $1 unless INVO has set another) applies to it. If you need a second economy, such as event tokens, track it in your own game. Only the INVO currency moves through purchases, transfers and payouts.
Can I change transfer limits after launch, and do changes affect pending transfers?
Yes. You can edit the minimum and maximum in the console at any time. Limits are checked only when a transfer or send is started, so transfers already in flight are not affected.
Can I archive a currency I no longer use?
No. There is no archive or delete for a currency. Because each title has exactly one currency, it lasts as long as the title does.
If my game lets players cash out, does INVO pay them?
No. Players never cash out through INVO. INVO pays you, the partner: your earned revenue goes to your bank account through withdrawals and partner payouts, free on INVO's payout schedule, or at 1% per payout capped at $500 when you request one on demand. If your platform pays creators, INVO can also verify and pay the creators you register, from the earnings INVO owes you, never from player balances; see creator payouts on the Partners page. Any other cash-out to players is yours to run, from your own funds and on your own terms.
Why should I price items in units rather than USD?
Purchases, transfers and your payouts all convert between units and USD at your currency's rate. When you price in units, you never have to set a dollar value on your token. For example, a 500-unit item is $50 at the default rate, and 1,000 units of your revenue are paid out as $100 before any payout fee.
Payment Methods
Which card networks does hosted checkout accept?
Hosted checkout accepts the major card brands, including Visa, Mastercard, American Express, Discover, JCB and Diners Club, wherever the card processor supports them. INVO does not limit which brands you can accept, and it records the brand on each saved card.
Are players charged in their local currency while my API amounts stay in USD?
No. INVO charges in USD only: GET /api/checkout/validate-game returns supported_currencies: ["USD"]. A player whose card is in another currency sees the conversion done by their card issuer. Your amounts, fees and webhooks are always in USD.
Do I need to do anything for 3D Secure and European SCA?
On hosted checkout you don't: the payment page runs the bank challenge when the issuer asks for one. On the direct route, a challenge comes back as status: "requires_action" with a client_secret. Complete it on the client, then call POST /api/currency-purchases/confirm-payment.
When a player saves a card, does that also authorize later charges without them present?
Yes. Saving a card records the cardholder's consent to be charged later without being present, which subscriptions rely on. Hosted checkout saves the card for later payments by default. POST /api/currency-purchases/setup-intent saves a card without charging anything.
Are Apple Pay and Google Pay enabled automatically?
Yes. On the card rail, the hosted checkout page offers Apple Pay, Google Pay and Link next to cards when the player's device and browser support them, with no setup on your side. Paying with a wallet costs the player the same as paying by card.
Which regional payment methods are offered, and are they turned on by player location?
The card rail (platform) offers cards, Apple Pay, Google Pay and Link. It does not offer regional bank or voucher methods. For regional and game-store payment methods, sell through the game storefront rail ("rail": "game"), which shows the methods available to the player on the payment page. On that rail the storefront is the seller.
What do the rail values platform, game and steam select, and which applies when I omit rail?
platform is INVO's card rail (cards and wallets), game is the game storefront rail with regional methods, and steam hands the purchase to the in-client platform store purchase flow. If you omit rail, INVO uses the rail of the storefront key you authenticated with, then your title's default payment rail, and finally platform. Every purchase webhook echoes the rail it used.
Do platform store builds have to use the steam rail?
Yes. Currency for a title distributed on the platform store must be bought through the platform store purchase flow (/api/currency-purchases/steam/*). Buying it another way is refused with 409 STEAM_PURCHASE_LAYER_REQUIRED, and a title that is not yet enabled for platform store gets 403 STEAM_RAIL_NOT_ENTITLED.
What are storefront keys (steam, ios, android, web, console), and what does RAIL_CHANNEL_MISMATCH mean?
A storefront key is an additional SDK key that names where a purchase came from: steam purchases route to the platform store rail, and the other four route to the card rail unless INVO has configured otherwise. All of a title's keys are valid at the same time, and each can be rotated or revoked on its own. 400 RAIL_CHANNEL_MISMATCH means you also sent a rail that disagrees with the key you authenticated with. Omit rail and the key decides.
Should I stop sending rail once I move to storefront keys?
Yes. With a storefront key, the key already sets the rail, so leave rail out. A rail that matches the key is accepted and ignored, and one that conflicts is refused with RAIL_CHANNEL_MISMATCH.
How long is the grace period when rotating a storefront key?
When you rotate a key, the old one keeps working for 7 days so you can redeploy. You can ask for immediate revocation with {"immediate": true}. Disabling a key stops it at once, with no grace period.
Can an ios or android storefront key ship inside the app?
No. Storefront keys are server-to-server credentials, exactly like your original SDK key. The name says where the purchase came from, not where the key is stored. Keep every key on your backend, and have your server pick the key that matches the player's storefront.
What do smart retries do with soft declines?
There is no automatic retry on a one-off purchase. A decline comes back as a failed payment (purchase.failed), and the player can try again or use another card, which is safe because declined attempts don't count toward spending limits. Automatic retry schedules exist only for subscription renewals.
Is there any PCI work left for me with hosted checkout?
With hosted checkout, card details are entered on INVO's page and never touch your servers, so you don't handle cardholder data. Your remaining work is to keep your SDK key on your backend and to verify webhooks. If you use the direct route and collect cards yourself, you must tokenise them client-side; raw card fields are not accepted.
Currency Purchase API
Read the Currency Purchase API page
Which route should I use to sell coins: hosted checkout, the direct route, platform store, or the game storefront rail?
- Use hosted checkout (
POST /api/checkout/sessions) unless you have a reason not to. - Use the direct route (
POST /api/currency-purchases/quote, then/purchase-currency) only if you already tokenise cards yourself. - platform store titles must use the platform store flow.
- For regional and game-store methods, use hosted checkout with
"rail": "game".
On the card routes you pay the card fee: 3.5% + $0.15 on Open, 5.0% + $0.15 on Merchant of Record, and 1.5% more on a card issued outside the US. Storefront rails carry no INVO card fee.
How many coins does a $10.00 purchase credit, and can INVO set a different rate for my currency?
A $10.00 purchase credits 100 coins at the default 10 per dollar. Coins are always priced from your list price, and the card fee is charged to you, not taken out of the coins. INVO can set a different rate for your currency only while it has no activity.
What is the $2.99 card minimum, when is it enforced, and what does min_purchase_warning look like before that date?
From 2026-10-27 (00:00 UTC), an INVO card purchase of coins below $2.99 is refused before any charge with 400 BELOW_CARD_MINIMUM, min_usd: "2.99" and enforced_from. Before that date the purchase goes through, and the response carries min_purchase_warning: { "code": "BELOW_CARD_MINIMUM", "min_usd": "2.99", "enforced_from": "2026-10-27", "message": "..." }. Purchases on the platform store and game storefront rails are unaffected.
What is the absolute $0.50 card floor, and where is it always enforced?
$0.50 is the smallest amount a card charge can settle for. Below it, the request is refused at any date with 400 BELOW_CARD_MINIMUM and min_usd: "0.50", with no enforced_from. It is checked when a hosted checkout session is created, on /complete and create-intent, on the direct route, and on Platform Commerce card sales.
Why does an unacknowledged $10.00 purchase now credit 100 coins instead of 93.70?
Under the earlier pricing, the card fee was deducted from the coins. Now the card fee is charged to you as the partner, so the player gets every coin you listed. The card is still charged exactly the usd_amount you send, and the response shows fee_taken_from: "partner".
Can a checkout session sell an item, and what does ITEM_METADATA_ON_CURRENCY_SESSION mean?
No. A checkout session always sells game currency, and metadata is only echoed back. If your metadata contains item_id or item_name, the 201 carries a warnings entry with code ITEM_METADATA_ON_CURRENCY_SESSION, and the session is still created as normal. To sell an item, use POST /api/platform-commerce/purchase.
How do I poll GET /api/checkout/sessions/{session_id}, and which credential does it need?
Send the session token as Authorization: Bearer <token>; X-Checkout-Session-Token or a JSON body {"token": ...} also work. Poll every 1 to 2 seconds until terminal is true. Possible errors:
- A token for another session is
403 SESSION_MISMATCH. - A card-saving token is
401 WRONG_SESSION_PURPOSE. - A token that expired more than 24 hours ago is
410 SESSION_EXPIRED.
The read is limited to 600 requests per minute per IP.
What do the six hosted checkout statuses mean?
pending: no payment yet.paid_pending_credit: charged, coins not yet in the balance.credited: charged and credited.refused: will not credit.refunded: money returned to the buyer.expired: never paid and can no longer be paid.
Only pending and paid_pending_credit are non-terminal. Treat any status you don't recognise as non-terminal and keep polling.
What should I show when a session is stuck in paid_pending_credit for over 60 seconds?
Keep polling, and show the order_id with a Contact support link. A stalled credit is recovered on INVO's side, and a failed poll is not a failed purchase. Never offer to charge again.
Which reason codes mean money was taken but coins were withheld, and why must I not offer a retry?
The CREDIT_REFUSED_* codes mean the charge happened but no coins were added: CREDIT_REFUSED_MINT_CEILING, CREDIT_REFUSED_UNKNOWN_RAIL, CREDIT_REFUSED_RATE_UNAVAILABLE, CREDIT_REFUSED_NO_PEG_BASIS, CREDIT_REFUSED_NO_PAYMENT_REFERENCE and CREDIT_REFUSED_WRONG_ORDER_TYPE. INVO's team is alerted, so a retry would only charge the buyer twice. Offer "Try again" only on PAYMENT_DECLINED and PAYMENT_CANCELLED, where nothing was charged.
On the direct route, what does POST /api/currency-purchases/quote return, and why must I send acknowledged_total_usd?
The quote returns:
total_usd: the price plus any tax, which is the figure to show the player.subtotal_usd,coins,currency_nameand abreakdown.processing_fee_usd: "0.00", because the player pays no card fee.partner_fee_usd: your card fee.international_card_fee_usd: always null on a quote, because no card is known yet.if_unacknowledged.
Send total_usd back as acknowledged_total_usd so the player is charged the total they agreed to, including tax where it applies. Without it, the card is charged exactly usd_amount and no tax is collected.
When do I get 409 QUOTE_STALE, and what does 409 TAX_PLACE_UNAVAILABLE mean on Merchant of Record?
QUOTE_STALE means the acknowledged_total_usd you sent is below the total INVO would charge (the body carries expected_total_usd). Re-quote, show the new total, and retry with a new purchase_reference. TAX_PLACE_UNAVAILABLE (Merchant of Record only, retryable: true) means INVO couldn't read the buyer's tax location from the card and you sent no country. Retry, or send the country and subdivision you quoted with. Nothing is charged in either case.
How should I generate purchase_reference, and why is a value starting with sub_ refused?
Generate one per logical purchase attempt, when the player taps Buy, and resend it on every retry. A UUID v4 works, up to 255 characters and unique within your title. A value starting with sub_ (any case) is refused with 400 CLIENT_REQUEST_ID_RESERVED, because that key space belongs to subscription renewals.
How do I tell a replayed success, an in-flight purchase and a spent identifier apart?
200 {"status": "success", "duplicate": true}: the purchase landed. Mark it complete.409 {"status": "pending", "duplicate": true}: still in flight. Keep waiting.409 {"status": "failed", "order_status": ...}: the identifier is spent on a terminal order. Only now retry, with a new identifier.
How do I complete 3D Secure on status requires_action, and why does a manual handle-next-action call fail?
1. Pass the returned client_secret to your card collector's confirm-payment call, which runs the bank challenge and confirms in one step. 2. Call POST /api/currency-purchases/confirm-payment with payment_intent_id and order_id to credit the coins.
The payment is created with automatic confirmation, so a separate manual "handle next action" call fails.
After confirm-payment, why should I check credited rather than already_processed?
credited: true means the coins are in the balance. already_processed is the inverse of credited, so it is also true whenever the call added nothing. On credited: false, read reason_code:
CREDIT_REFUSED_*: the money was taken and the coins were withheld.REFUNDEDorCHARGEBACK_LOST: the payment was returned.null: the credit is still finishing. PollGET /api/currency-purchases/order-details.
What is PAYMENT_OUTCOME_UNKNOWN, and why must I retry with the same purchase_reference?
INVO sent the charge but never learned the result, so the card may have been charged. You get a 503 or 502 with order_id, retry_with_same_reference: true and poll_endpoint. Retry with the same purchase_reference, or poll order-details: a new identifier creates a new order and could charge twice. A charge that did land is found and credited automatically, so keep the player on "processing".
How do I tell a circuit breaker 503 from PAYMENT_OUTCOME_UNKNOWN?
A circuit breaker 503 has no error code: the body is {"status": "service_unavailable", ...}. It means no charge exists, so a fresh identifier is safe. PAYMENT_OUTCOME_UNKNOWN always carries that error_code, which means you must reuse the same identifier. Switch on the code, not the HTTP status.
Which error field should new integrations match on?
Match on error_code_public. It is provider-neutral, for example PROCESSOR_UNAVAILABLE, PROCESSOR_ERROR, RAIL_DISABLED, RAIL_CHANNEL_MISMATCH and BELOW_CARD_MINIMUM. The original error_code is still sent unchanged, so existing matches keep working.
How are refunds calculated when the player has spent some coins, and can a refund take a balance below zero?
Only unspent coins can be refunded. If the player spent part of the purchase, the refund covers the unspent part in proportion, and a fully spent purchase can't be refunded (409 COINS_ALREADY_SPENT). Coins that left the balance after the purchase count as spent first. A refund never takes a balance below zero. Refunds of card coin purchases are issued by INVO: contact support with the order_id.
On a full $10.00 refund, how much goes back to the card, and is my $0.65 card fee returned?
For purchases made under the current pricing, a full refund returns the whole $10.00 to the card by default. The purchase.refunded webhook shows usd_refunded_gross, usd_refunded_net and processing_fee_retained_usd so you can see exactly what went back. Older purchases made under the previous pricing could have card processing kept, which is how a $9.41 net figure arises. Your $0.65 card fee is not returned on a refund or a chargeback.
What happens when a player disputes a coin purchase after spending the coins?
If the dispute is lost, INVO takes back the coins the player still holds from that purchase; the player's balance never goes below zero. The value of the coins the player had already spent is charged to your earnings at the price the player paid per coin, before tax, and reported as spent_coins_charged_usd on the closing purchase.disputed and on the dispute in GET /api/dev/disputes ("0.00" if none). INVO's card fee is not returned. Nothing is charged to you when the chargeback guarantee covers the dispute.
Which webhooks should I handle for coin purchases?
purchase.completed: coins credited. Don't grant anything extra.purchase.failed: declined or cancelled. Nothing moves.purchase.refunded: unspent coins debited, never below zero.purchase.disputed: sent when a chargeback opens and again when it closes, withdispute_status. A lost dispute debits the coins the player still holds from the purchase, never below zero, and charges you for coins already spent (spent_coins_charged_usd) unless the chargeback guarantee covers it.purchase.fraud_warning: advisory only.
There is no purchase.dispute_lost event: a lost dispute arrives as purchase.disputed with dispute_status: "lost".
Platform Store Payments
Read the Platform Store Payments page
What changed in September 2026 about platform store setup?
Currency now sells through your own platform store app, and the store pays your store partner account. Previously, purchases ran through a single INVO-owned app. You now register your app with INVO and keep a payment method on file. INVO issues the player's currency as soon as the charge is captured, then settles with you.
What do I need before enabling platform store?
You need:
- A store partner account with in-game purchases (microtransactions) enabled for the app.
- A publisher Web API key whose publisher group includes that app.
- An accepted billing authorisation and a payment method on file in the INVO dashboard.
Enter the app id and key in the title's platform store settings, then ask INVO to enable the rail. Until everything is in place, purchases are refused before the player is charged (503 STEAM_NOT_CONFIGURED or 409 PARTNER_BILLING_NOT_SET_UP).
Why does a personal platform store Web API key fail verification?
Before saving, INVO checks with the platform store that the key controls the app id you entered. Only a publisher (group) Web API key whose group includes the app passes. A personal user key does not control the app, so the pair is rejected straight away.
Why does INVO need a payment method on file when the player pays platform store directly?
On platform store, the money goes to you rather than to INVO, yet INVO still issues the player's currency immediately. INVO is then owed the value behind that currency. It settles with you in batches, itemised in your dashboard against the purchases that produced each charge. A bank account costs less to settle than a card.
In the $100 example, why does the player get 700 units, and what do I pay in processing?
Units come from the part of the price you actually receive: price, less VAT, less the store's share, times 10 units per dollar (rounded down). So $100 with no VAT gives $70, which is 700 units. A real $99.99 pack in a zero-VAT country gives 699. INVO then settles that $70 with you, plus the processing cost of collecting it from your payment method on file, which is lower for a bank account than a card.
What are the three steps of the platform store flow, and which run on my server?
1. Your server calls POST /api/currency-purchases/steam/init-purchase. 2. The player approves the charge in the platform store, and your game client receives the MicroTxnAuthorizationResponse_t callback. 3. Your server calls POST /api/currency-purchases/steam/finalize-purchase with the order_id.
Steps 1 and 3 run on your server with your X-Game-Secret-Key. The client only handles the platform store callback.
Why is no price sent to init-purchase?
INVO owns platform store pricing. You send a pack_id from GET /api/currency-purchases/steam/packs, and INVO sets the price and the currency amount. An unknown pack returns 400 UNKNOWN_STEAM_PACK with the valid ids.
Why does the same $9.99 pack give 69 coins in the US and 58 in France, and why must I always pass steamid to the packs call?
Platform store prices include VAT where VAT applies, so in a high-VAT country more of the fixed price goes to tax and less is left to buy currency. Pass the player's steamid to /steam/packs so the amounts reflect their country. Without it, amounts are quoted with no VAT taken off, and your store would promise more than the purchase delivers.
Can I cache the platform store pack catalog or hard-code amounts?
No. Pack prices and VAT rates are configuration that can change without an SDK release, and amounts vary by player country. Fetch the catalog per player, and if a pack disappears, re-fetch (UNKNOWN_STEAM_PACK lists the valid ids).
Which metadata keys are reserved on the platform store, and what is the size limit?
metadata can be at most 8 KB once serialised. On the platform store rail, INVO adds steam_wallet_currency, steam_country, steam_account_status and vat_rate_pct to the echoed object, so don't use those names. Keys starting with _invo_ are reserved and removed.
How do I correlate a platform store purchase.completed with my order, and why not use m_ulOrderID?
Put your own player or order ids in metadata on init-purchase. They come back at data.metadata on purchase.completed. purchase_reference is not echoed. m_ulOrderID is the platform store's own order id: INVO's init response doesn't return it, so you have nothing to match it against. Track the order_id from init instead.
When should I use usersession: "web", and whose IP goes in player_ip?
Use "web" when the platform store's in-game overlay can't render, for example in embedded-browser builds. Platform store then returns steam_checkout_url, which you open in a top-level browser tab, never an iframe. player_ip is required in that case and must be the player's IP address, not your server's (400 MISSING_PLAYER_IP if omitted).
Why can't the web approval be tested in sandbox?
the platform store's sandbox issues browser-checkout URLs that its production checkout host won't serve. That is a limitation on the platform store's side. Everything up to the URL works in sandbox, and everything after approval is the same code the overlay flow uses. Test the loop with usersession: "client", and treat the first production web approval as a go-live check.
Why doesn't finalize status "success" alone prove currency was credited?
status: "success" means platform store captured the payment. Show "purchase complete" only when credited is true. credited: false with a CREDIT_REFUSED_* reason means the money was taken and no currency was added, so don't ask the player to pay again. With reason_code: null, poll order-details.
What does 409 not_authorized with steam_status Init mean, and when should I stop polling?
Init means the player hasn't approved yet. That is the normal wait state, so call again after the callback or on your poll interval. Any other steam_status (such as Cancelled, Failed, Refunded or Chargedback) is final, so stop. Also stop on 400 "Order is not finalizable" or 404.
What do PARTNER_CREDIT_UNAVAILABLE, PARTNER_RAIL_SUSPENDED and PARTNER_BILLING_NOT_SET_UP mean?
All three are 409 refusals on init-purchase, sent before the player is charged:
PARTNER_CREDIT_UNAVAILABLE: your settlement balance can't cover the purchase right now. A top-up in the dashboard fixes it, so don't poll in a loop, and reuse the samepurchase_referenceon a manual retry.PARTNER_RAIL_SUSPENDED: settlement is on hold until it is resolved in the dashboard.PARTNER_BILLING_NOT_SET_UP: the billing authorisation or payment method is missing.
If my backend crashes before finalize, does the player lose the purchase?
No. INVO's reconciliation finds authorized platform store transactions on its own, captures them and credits the player. A missed finalize only delays the credit.
How are platform store refunds and chargebacks reported?
A refund arrives as purchase.refunded with rail: "steam" and steam_transid. A partial refund carries fully_refunded: false. A chargeback arrives once, as purchase.disputed with dispute_status: "lost", currency_debited, new_balance, rail: "steam", steam_transid, and payment_intent_id, charge_id and dispute_id set to null. Both debit only what the player still holds from that purchase, and never below zero.
How do transfers and sends work for platform store players who can't use passkeys?
The store client can't run an in-client passkey, so transfers and sends use the QR device-approval flow: the player scans a code and approves on their phone. See Device Approval (Consoles & TVs).
Item Purchase API
Read the Item Purchase API page
What does POST /api/item-purchases/purchase-item do, and why is no card involved?
It spends coins the player already holds on one of your items, in a single atomic request from your server. No money changes hands at that moment: the coins were paid for when they were bought or received. INVO takes its fee (5% by default) out of the item price, and the rest is your revenue.
Which fields are required, and which are optional?
Required: client_request_id, player_email, player_name, item_id, item_name, item_quantity (1 to 1000), unit_price and total_price. Optional: item_description, item_category and player_phone. Authenticate with X-Game-Secret-Key, from your server only.
How must total_price relate to quantity and unit price?
total_price must equal unit_price × item_quantity, rounded to cents. A difference of more than 0.01 is refused with a 400.
When do I get 400 AMOUNT_BELOW_MINIMUM?
When the spend is so small that INVO's fee would round to zero. This only applies when your fee rate is above zero. Raise the price, or bundle the item.
What is the revenue split, and are developer_revenue and platform_fee in coins or USD?
The default split is 95% to you and 5% to INVO, and INVO can set a custom rate for your title. Both figures in financial_breakdown are in your currency's coins, not USD. They convert to USD at your currency's rate when you are paid out.
Should I read new_balance or balance_info.new_balance?
Read the top-level new_balance, which is the canonical post-purchase balance. balance_info.new_balance carries the same value for older integrations.
What does a reused client_request_id return, and does the 409 include the original transaction?
A reused id returns 409 with a message saying the purchase was already processed, and no new spend happens. The 409 does not include the original transaction. Look it up with GET /api/item-purchases/order-details?client_request_id=....
Why is a client_request_id starting with sub_ refused?
The sub_ prefix (any case) is reserved for subscription renewal charges, which share the same idempotency key space. It is refused with 400 CLIENT_REQUEST_ID_RESERVED. UUIDs are unaffected.
What error comes back for insufficient balance?
A 400 with required_amount and current_balance in the body, and no error_code. Nothing is deducted. Show the player how many more coins they need.
Can purchase-item create a new player?
Yes. If the email isn't yet a player in your title, the player is created on this call using player_name, plus player_phone if you send one. A new player starts with a zero balance, so the purchase itself then fails for insufficient balance.
How do I page through purchase history, and what does order-details return?
GET /api/item-purchases/player-purchase-history?player_email=...&limit=&offset= pages with limit (default 50, maximum 100) and offset, and returns has_more. GET /api/item-purchases/order-details takes exactly one of order_id, transaction_id or client_request_id. It returns the order, a financial summary and a status timeline.
Why might item purchases be refused for a while?
Repeated failing attempts or unusually rapid purchasing can be temporarily refused. The refusal is a 429 carrying retry_after in seconds: wait that long and do not retry in a loop. Validate each request before you send it (required fields, and total_price equal to unit_price times item_quantity).
Are item purchases gated by guardian approval for minors?
No. That is deliberate. Guardian approval applies when value enters a minor's wallet (a guardian-approved transfer or send), so spending coins on items is not gated a second time.
Can a player get a refund for an item bought with coins?
Not through INVO today. Item purchases are not refundable on INVO's side. If you want to compensate a player, do it in your own game, for example by granting the item again.
Is there a webhook for completed item purchases?
Yes, item.purchased. It is emitted in the same commit as the spend. Subscribe to it, or use ["*"].
Platform Commerce API
Read the Platform Commerce API page
Why does a standard game account get 403 NOT_A_PLATFORM_TENANT?
Platform Commerce is only for accounts INVO has tagged as platforms (non-game apps such as creator stores and marketplaces). A game sells items for coins through the Item Purchase API and sells coins through Currency Purchase.
What fields does POST /api/platform-commerce/purchase require?
Required: client_request_id, funding_source ("balance" or "card"), player_email, player_name, item_id, item_name, item_quantity (1 to 1000), unit_price and total_price. Optional: item_description, item_category and player_phone. On the card leg you can also send success_url, cancel_url and metadata.
How does total_price differ between the balance leg and the card leg?
On the balance leg, total_price is in your currency units and is deducted from the user's balance. On the card leg it is in USD and is exactly what the buyer is charged, with tax added only where INVO collects it on Merchant of Record. On both legs it must equal unit_price × item_quantity.
What does the balance response return, and what does the card response return instead?
A balance purchase settles in the same request. It returns 200 with status: "success", order_id, transaction_id, new_balance, previous_balance and financial_breakdown. A card purchase returns 200 with status: "requires_payment", session_id, checkout_url, expires_at and amount_usd. Send the buyer to checkout_url.
Why should a card sale be final only on platform_commerce.purchased, not on the return to success_url?
The browser return is not a verified record of payment: a buyer can reach or skip it independently of the charge. INVO records the sale only after it verifies the payment, and that is when it sends platform_commerce.purchased. Fulfil on the webhook, and use GET /purchase-status/{order_id} as a fallback.
What are the fee rates for balance and card sales on Open, and what does a $50.00 card sale leave as developer_revenue?
On Open, a balance sale costs 3.5% and a card sale costs 3.5% + $0.15, plus 1.5% if the card was issued outside the US. All fees come from your proceeds, not the buyer's bill. A $50.00 card sale carries a $1.90 fee and leaves $48.10 as developer_revenue, or $47.35 on a non-US card.
What changes for Merchant of Record card sales?
On Merchant of Record, INVO is the seller and handles consumption tax. The card fee is 5.0% + $0.15 (plus 1.5% on a non-US card), so a $50.00 sale leaves $47.35, and INVO issues the buyer a receipt and a refund receipt (receipt_number, receipt_url). 10% of your developer_revenue on each card sale is held as a chargeback reserve for 90 days. Fraud-coded chargebacks are covered by the chargeback guarantee. The balance leg is not held and gets no receipt.
How do the $2.99 minimum and the $0.50 card floor differ?
$0.50 is the smallest amount a card charge can settle for, and it is refused at any date. $2.99 is INVO's minimum card sale: it is enforced from 2026-10-27, and before then a smaller sale goes through with a min_purchase_warning. Both refusals are 400 with error_code_public: "BELOW_CARD_MINIMUM" and min_usd naming the floor that applied. The balance leg has neither minimum.
Where should I read the card maximum, and what do ABOVE_CARD_MAXIMUM and AMOUNT_BELOW_MINIMUM mean?
Read max_amount from GET /api/checkout/validate-game rather than hard-coding it. It is a platform setting, currently $100,000.00 by default, and can differ between sandbox and production. 400 ABOVE_CARD_MAXIMUM means the card total is over it. 400 AMOUNT_BELOW_MINIMUM means the sale is so small that INVO's fee would round to zero.
How does GET /purchase-status/{order_id} report status for each leg?
It returns order_id, status (completed, pending_payment or refunded), payment_method (card or network_currency) and created_at. A balance sale's amount is in game_currency_amount, and a card sale's is in usd_amount. On Merchant of Record card sales it also carries receipt_number and receipt_url once the receipt is issued.
How do I page through purchase history with limit, cursor, next_cursor and has_more?
Call GET /api/platform-commerce/purchase-history?player_email=...&limit=... with limit from 1 to 100 (default 50), newest first. While has_more is true, pass the previous page's next_cursor as cursor. An unknown player is 404 PLAYER_NOT_FOUND, and a bad cursor is 400 INVALID_CURSOR.
Where can I see INVO's fee on each Platform Commerce sale?
Each purchase on GET /api/platform-commerce/purchase-history and on the purchase status read carries invo_fee_usd and partner_share_usd. invo_fee_usd is everything INVO kept on the sale, tax excluded: the card fee plus any international card fee on a card sale, the platform fee on a balance sale. partner_share_usd is your earnings from the sale. Both are null until the sale has settled.
How does a refund work for a balance sale versus a card sale?
Call POST /api/platform-commerce/refund with order_id or client_request_id, plus an optional reason. A balance refund re-credits the buyer's wallet and returns refunded_amount, new_balance and developer_revenue_reversed. A card refund returns the full price to the card and returns refunded_amount_usd, developer_revenue_reversed_usd, card_fee_charged_to_partner_usd and partner_debited_usd. Only a completed order can be refunded.
On a card refund, why is fee_retained true, and how do card_fee_charged_to_partner_usd and partner_debited_usd relate?
INVO keeps its card fee on a refund, so the fee stays charged to you while the buyer gets the full price back. card_fee_charged_to_partner_usd is the card fee portion of this refund, including any international fee. partner_debited_usd is everything the refund takes from your earnings: your revenue reversed plus that fee. For example, on a $5.00 Open sale it is $4.67 + $0.33 = $5.00.
How do partial card refunds work, including REFUND_REMAINING_REQUIRES_CONFIRMATION and the 120-second confirm rule?
A card sale is only refunded in parts when part of the charge was already refunded outside INVO's API. The next /refund call then records that refund, and the response carries partial_refund, refunded_total_usd, charge_total_usd and remaining_usd. Two confirmations apply, and in both cases nothing moves until you confirm:
- To refund the rest, send
"refund_remaining": true. Otherwise you get409 REFUND_REMAINING_REQUIRES_CONFIRMATION. - Within 120 seconds of the last refund on the order, a further refund also needs
"confirm_additional_refund": true. Otherwise you get409 REFUND_RECENTLY_ISSUED.
What should I do after 502 REFUND_NOT_COMPLETED?
Retry the same refund. Nothing was recorded, because the refund failed, was canceled or still needs an action on the payment side. A refund that was in fact created is found and recorded on the retry, never issued twice. A 502 with no error_code means the payment side couldn't be reached, and you handle it the same way.
What happens to my revenue on a lost chargeback?
While a dispute you are responsible for is open, your share of the sale is held (counted in dispute_holds_usd). On a lost card-sale chargeback your share of the sale is reversed: it is cancelled if not yet paid out, or deducted from future payouts, and the hold is released as the reversal is booked, so it is taken once. The card fee stays charged to you. On a card dispute opened from 2026-10-21, on either tier, the card network's dispute fee is also passed through to you at cost (dispute_fee_usd, dispute_fee_charged_to_partner on the closing purchase.disputed). On Merchant of Record, a fraud-coded chargeback is covered by the guarantee, and you keep your share (guarantee_covered: true). The same applies to Open-tier sales made before 2026-10-21. A dispute that is not coded as fraud is yours on both tiers.
How does idempotency differ between the balance and card legs?
On the balance leg, a repeated client_request_id returns 409 because it was already processed. On the card leg, a repeat returns the same checkout session with idempotent_replay: true, so a retry can't create a second charge. If a payment already exists and the session can't be returned, you get 409 DUPLICATE_CLIENT_REQUEST_ID with order_id and order_status. sub_ prefixes are refused on both legs.
Which webhook fields show the fee split on platform_commerce.purchased and platform_commerce.refunded?
On platform_commerce.purchased, fee_breakdown carries total_paid, platform_fee, developer_revenue and international_card_fee_usd. platform_fee already includes the international fee, so platform_fee + developer_revenue = total_paid. On platform_commerce.refunded, look at fee_retained, developer_revenue_reversed and amount_unit (usd or currency). Card refunds also carry card_fee_charged_to_partner_usd and partner_debited_usd.
Back to the FAQ hub. Something not answered here? Email partners@invo.network.