Write-Response Contract: new_balance
Every Invo endpoint that credits or debits a player's balance returns the post-write balance at one canonical location: response.new_balance. Read from there.
Why this exists
Wallet UIs need to display the player's new balance immediately after every write. Without a single agreed location, partners had to special-case each endpoint (purchase_details.new_balance, send_details.new_balance, transfer_details.new_balance, etc.). One missed branch = a stranded wallet showing a stale number. The canonical location eliminates that class of bug — same field name, same place, every endpoint.
Where to read it
| Endpoint | Side affected | new_balance reflects |
|---|---|---|
| /api/currency-purchases/purchase-currency | Player credit | Player's balance after the purchase credit |
| /api/item-purchases/purchase-item | Player debit | Player's balance after the item debit |
| /api/transfers/initiate-transfer | Source player reservation | Source's available_balance post-reservation |
| /api/transfers/verify-sms | no funds movement | Source's available_balance (re-confirmation) |
| /api/transfers/claim-transfer | Target player credit | Target's available_balance post-credit |
| /api/currency-sends/initiate-send | Sender reservation | Sender's available_balance post-reservation |
| /api/currency-sends/verify-sms | no funds movement | Sender's available_balance (re-confirmation) |
| /api/currency-sends/claim-currency | Receiver credit | Receiver's available_balance post-credit |
Field shape
Type
Decimal-as-string. Always parse defensively:
// JS / TS
const newBalance = response.new_balance != null
? new Decimal(response.new_balance)
: null;
# Python
from decimal import Decimal
new_balance = (
Decimal(response["new_balance"])
if response.get("new_balance") is not None
else None
)Units
Game currency (the destination tenant's branded currency). The currency_name field in the same response tells you which one.
Not USD. If you display in USD on your wallet UI, convert using the destination tenant's exchange rate.
When new_balance is null
Only one case: POST /api/currency-purchases/purchase-currency returning status: "requires_action" (3D Secure / SCA). The user must complete additional authentication; no credit has happened yet, so there's no new balance to report. The field is explicitly null (not missing) so your code can branch on status:
if (response.status === 'success' && response.new_balance != null) {
// safe to update wallet UI
updateWalletBalance(new Decimal(response.new_balance));
} else if (response.status === 'requires_action') {
// 3DS challenge — pass response.client_secret to Stripe.js
await stripe.handleCardAction(response.client_secret);
}On every other success path (200/201 with status: "success"), new_balance is non-null. The field is always present in success responses — never undefined.
Belt-and-suspenders: trust GET /player-balances
Recommended pattern
Use the response's new_balance for fast wallet updates, but treat GET /api/player-balances/player/by-email/<email> as the source of truth. After any successful write, kick off a background read to reconfirm. Mid-deploy, network hiccup, or a stale write-response shape will never strand a user as long as you re-read.
async function purchaseAndUpdate(usdAmount) {
const response = await api.purchaseCurrency({ usd_amount: usdAmount });
if (response.status === 'success' && response.new_balance != null) {
// fast path: optimistically update from write response
setWalletBalance(new Decimal(response.new_balance));
}
// belt: always reconcile against authoritative read endpoint
const fresh = await api.getPlayerBalance({ player_email });
setWalletBalance(new Decimal(fresh.available_balance));
}Backwards-compatible aliases
Partners that integrated before the canonical location was nailed down can still read from the legacy nested fields. Both are populated to the same value; new integrations should use the top-level field.
| Endpoint | Legacy location |
|---|---|
| /currency-purchases/purchase-currency | purchase_details.new_balance |
| /item-purchases/purchase-item | balance_info.new_balance |
| /transfers/claim-transfer | transfer_details.new_balance |
| /currency-sends/claim-currency | send_details.new_balance |