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

EndpointSide affectednew_balance reflects
/api/currency-purchases/purchase-currencyPlayer creditPlayer's balance after the purchase credit
/api/item-purchases/purchase-itemPlayer debitPlayer's balance after the item debit
/api/transfers/initiate-transferSource player reservationSource's available_balance post-reservation
/api/transfers/verify-smsno funds movementSource's available_balance (re-confirmation)
/api/transfers/claim-transferTarget player creditTarget's available_balance post-credit
/api/currency-sends/initiate-sendSender reservationSender's available_balance post-reservation
/api/currency-sends/verify-smsno funds movementSender's available_balance (re-confirmation)
/api/currency-sends/claim-currencyReceiver creditReceiver'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.

EndpointLegacy location
/currency-purchases/purchase-currencypurchase_details.new_balance
/item-purchases/purchase-itembalance_info.new_balance
/transfers/claim-transfertransfer_details.new_balance
/currency-sends/claim-currencysend_details.new_balance