Platform Commerce
Sell items on your platform through Invo, with two ways to pay: a user's existing Invo-network balance, or a direct card payment on Invo's hosted checkout. Funding is resolved on your server, so network value always moves through Invo — never around it.
Who this is for: platform tenants (non-game apps — content platforms, creator stores, marketplaces). Every endpoint below is available only to platform-tagged accounts; a standard game account receives 403. For in-game item spending on a game account, use the Item Purchase API instead.
Two funding legs
Balance
The user spends Invo-network currency they already hold. A ledger debit — no card, settles synchronously (the response is the completed sale). Amount is in your currency units. Invo fee: 3.5% flat.
funding_source: "balance"Card
A direct card payment (USD) on Invo's hosted checkout — card, Apple Pay, Google Pay, 3-D Secure, and international billing address all handled by us. Settles asynchronously on a verified webhook. Invo fee: 3.5% + $0.30.
funding_source: "card"Authentication
All Platform Commerce endpoints are called from your server with your secret key:
X-Game-Secret-Key: your_secret_key_hereNever call these from a client, and never ship the secret key in a browser or app bundle. Funding is resolved server-side; for the card leg the browser only ever receives a single-use checkout_url.
1. Create a purchase
/api/platform-commerce/purchase
Request body
| Field | Type | Req. | Notes |
|---|---|---|---|
client_request_id | string | yes | Your idempotency key. A repeat with the same id is de-duplicated. |
funding_source | string | yes | "balance" or "card". Validated + enforced server-side. |
player_email | string | yes | The buyer's email. |
player_name | string | yes | ≤ 255 chars. |
item_id | string | yes | Your item identifier. ≤ 255 chars. |
item_name | string | yes | Shown to the buyer on the card checkout. ≤ 255 chars. |
item_quantity | int | yes | 1–1000. |
unit_price | decimal | yes | Rounded to 2 dp. |
total_price | decimal | yes | Must equal unit_price × quantity. Card: USD, $0.50–$999.99. Balance: your currency units. |
item_description, item_category | string | no | item_category ≤ 100 chars. |
player_phone | string | no | Optional. |
success_url, cancel_url | string | no | Card only — where to send the buyer after checkout. |
metadata | object | no | Card only — your correlation data, echoed back to you. |
Note: the card leg does not take a billing address — the buyer enters it on Invo's hosted checkout and we capture it from the confirmed payment.
Balance response — settled (200)
{
"status": "success",
"funding_source": "balance",
"order_id": 123,
"transaction_id": "TXN_...",
"new_balance": "45.00",
"previous_balance": "50.00",
"currency_name": "Gems",
"financial_breakdown": {
"total_paid": "5.00",
"developer_revenue": "4.82",
"platform_fee": "0.18"
}
}Card response — send the buyer to checkout (200)
{
"status": "requires_payment",
"funding_source": "card",
"session_id": "<id>",
"checkout_url": "https://invo.network/checkout?session=<token>",
"expires_at": 1690000000,
"amount_usd": "5.00"
}Card leg: send the buyer to the hosted checkout
A card purchase is not paid when this returns — the order does not exist yet. Send the buyer to checkout_url; they pay on Invo's hosted page. Treat the sale as final only when you receive the platform_commerce.purchased webhook. The link expires at expires_at (15 minutes).
2. The card checkout flow
- Your server calls
POST /purchasewithfunding_source: "card"and gets acheckout_url. - Send the buyer to it — either redirect (
window.location = checkout_url) or embed it in an iframe. - The buyer enters card + billing address on Invo's hosted checkout (card / Apple Pay / Google Pay / 3-D Secure) and pays.
- Invo verifies the payment, records the sale + fee, captures the billing address, and fires
platform_commerce.purchased. - The buyer is returned to your
success_url(orcancel_url). Fulfil the item on the webhook, not on the browser return.
Embedding (optional)
If you iframe the checkout, listen for the completion message from Invo's page:
window.addEventListener('message', (e) => {
if (e.data && e.data.type === 'INVO_CHECKOUT_COMPLETE') {
// e.data.data.status === 'completed'
// Show a confirmation UI — but still fulfil on the webhook.
}
});3. Check status
/api/platform-commerce/purchase-status/{order_id}
Poll an order (useful as a fallback to the webhook for card purchases). Returns:
{
"order_id": 123,
"status": "completed", // completed | pending_payment | refunded
"game_currency_amount": "5.00", // balance leg
"usd_amount": "5.00", // card leg
"payment_method": "card", // card | network_currency
"created_at": "2026-07-20T..."
}4. Refund
/api/platform-commerce/refund
Refund a completed purchase. Body: { "order_id": 123 } or { "client_request_id": "..." }, plus optional reason.
{
"status": "refunded",
"funding_source": "card", // or "balance"
"order_id": 123,
"transaction_id": "TXN_...",
"refunded_amount_usd": "5.00", // card; balance -> "refunded_amount" + "new_balance"
"stripe_refund_id": "...", // card only
"fee_retained": true
}The customer is made whole — a balance purchase re-credits the wallet, a card purchase refunds the card.
Invo retains its fee on refunds (fee_retained: true).
Only a completed order is refundable; a second refund on the same order returns 409.
Error codes
| HTTP | Code | Meaning |
|---|---|---|
| 400 | — | Validation (missing/invalid field, price mismatch, bad email). |
| 400 | AMOUNT_BELOW_MINIMUM | Fee rounds to zero — amount too small. |
| 400 | BELOW_STRIPE_MINIMUM | Card total below $0.50. |
| 400 | ABOVE_CARD_MAXIMUM | Card total above $999.99. |
| 401 | — | Missing or invalid secret key. |
| 403 | NOT_A_PLATFORM_TENANT | This API is for platform tenants only. |
| 409 | — | Duplicate client_request_id / order already refunded. |
| 429 | — | Rate limited. |
| 503 | service_unavailable | Temporarily unavailable — retry shortly. |
Webhooks
Subscribe (see Webhook Management). Card purchases must be fulfilled here — this is the source of truth, not the browser return.
platform_commerce.purchased
{
"event": "platform_commerce.purchased",
"data": {
"transaction_id": "TXN_...",
"order_id": 123,
"funding_source": "card", // or "balance"
"player_email": "buyer@example.com",
"identity_id": "...",
"item_id": "sticker_pack_01",
"item_name": "Sticker Pack",
"item_quantity": 1,
"total_price_usd": "5.00", // card; balance -> "total_price" + "currency_name" + "new_balance"
"client_request_id": "...", // card
"fee_breakdown": {
"total_paid": "5.00", "platform_fee": "0.30", "developer_revenue": "4.70"
}
}
}platform_commerce.refunded
{
"event": "platform_commerce.refunded",
"data": {
"order_id": 123,
"funding_source": "card",
"player_email": "buyer@example.com",
"identity_id": "...",
"refunded_amount": "5.00",
"amount_unit": "usd", // "usd" (card) or "currency" (balance)
"fee_retained": true
}
}Idempotency. Always send a stable client_request_id.
Balance: a repeat returns 409 (already processed).
Card: a repeat returns the same checkout session (idempotent_replay: true) so a retry can't create a second charge.
Fees. Balance leg: 3.5% flat. Card leg: 3.5% + $0.30.
Your revenue on each sale is the total minus the Invo fee, returned in financial_breakdown (balance response) and on the purchase webhook (fee_breakdown).