Endpoints
The Catalog, Purchase, and Player API endpoints your game backend implements for a Real-time Catalog integration, and how Stash calls them.
Stash calls these endpoints on your game backend. Implement them as described in Getting Started.
Catalog API
Retrieve dynamic, player-specific product catalogs.
GetCatalog
GET /api/v1/catalog: returns the complete product catalog for a given platform, region, language, and playerId. The response is structured as sections (grids or carousels) containing PurchasableItem, OfferChainItem, or NonPurchasableItem entries.
For the full request parameters and response schema, see the GetCatalog API Reference.
GetOfferDetails
GET /api/v1/catalog/offer: returns detailed offer information including drop rates for loot boxes, required for legal compliance in many jurisdictions.
For the full request parameters and response schema, see the GetOfferDetails API Reference.
Purchase API
Handle the complete purchase lifecycle with three endpoints.
Purchase Flow
For where the player's money is at each step, why ConfirmPayment is the point where your backend can still decline a purchase, and which checks only your backend can enforce, see Payment Flow.
Register and cancel are optional
RegisterPayment and CancelPayment are on or off together. By default Stash calls RegisterPayment before ConfirmPayment, and CancelPayment when a registered web shop cart purchase does not complete.
If your backend does not need a reserve step, ask your Stash engineering partner to turn both off for your shop. Stash then calls neither, and ConfirmPayment is the first request that carries the purchase's transaction ID. Stash can also turn them off only for checkout link purchases (Stash Pay and Buy Now), so web shop carts keep them.
RegisterPayment
POST /api/v1/purchase/register (Step 1): registers a purchase intent. Your backend should reserve inventory for the player and respond with one of the inline status codes (e.g. SUCCESS_REGISTERED, FAILED_INSUFFICIENT_INVENTORY, FAILED_PURCHASE_LIMIT_EXCEEDED, FAILED_INVALID_PRICE).
For the full list of status codes and the complete request/response schema, see the RegisterPayment API Reference.
ConfirmPayment
POST /api/v1/purchase/confirm (Step 2A): confirms and completes the purchase after successful payment. Your backend grants items to the player and acknowledges the transaction.
Unified Integration: This endpoint is the same API used for StashPay integrations. Implement once to power both your WebShop and StashPay checkout links.
Declining a confirm. To refuse the purchase, respond with an HTTP 4xx status and a body that carries a decline object naming a PurchaseConfirmationStatus, and omit results. Stash never charges the player for a declined confirm. Where the checkout supports it, Stash uses the status to pick the message the player sees (for example PURCHASE_CONFIRMATION_STATUS_FAILED_INSUFFICIENT_INVENTORY, PURCHASE_CONFIRMATION_STATUS_FAILED_PURCHASE_LIMIT_EXCEEDED, PURCHASE_CONFIRMATION_STATUS_FAILED_INVALID_PRICE); otherwise the player sees a generic failure message. A 4xx without a decline still declines the purchase, but carries no structured reason.
{
"decline": {
"status": "PURCHASE_CONFIRMATION_STATUS_FAILED_PURCHASE_LIMIT_EXCEEDED",
"productId": "gold_1000",
"message": "player has reached the purchase limit for this item"
}
}Use PURCHASE_CONFIRMATION_STATUS_FAILED_PACK_LIMIT_EXCEEDED when the purchase allowance shared by every item of a purchase pack is exhausted, and PURCHASE_CONFIRMATION_STATUS_FAILED_PURCHASE_LIMIT_EXCEEDED when only the item's own limit is. Return exactly one of results or decline; a body carrying both is treated as declined.
For the full request/response schema (including the decline object, its status values, and optional fields like extraInGameCurrency, extraLoyaltyPoints, and emailMarketingOptIn), see the ConfirmPayment API Reference.
CancelPayment
POST /api/v1/purchase/cancel (Step 2B, failure path): cancels a pending purchase. Your backend should release reserved inventory so the player can retry.
See the CancelPayment API Reference for the full request/response schema.
Loyalty snapshot on purchase requests
When the shop runs a live loyalty program, the RegisterPayment and ConfirmPayment request bodies carry a loyalty snapshot (type PlayerLoyaltyState): the same object your backend already receives on loyalty webhooks. This gives your backend the player's XP balance, tier, and tier-relative earn multiplier at purchase time, in-band with the register/confirm calls, so it can apply tier-aware logic (for example bonus sizing) without a separate lookup.
"loyalty": {
"campaignId": "spring_2026",
"totalPoints": 1250,
"currentTierId": "silver",
"pointsMultiplierPermille": 2000,
"pointsToNextTier": 750,
"pointsToNextMilestone": 250,
"nextMilestone": {
"milestoneId": "milestone_uuid",
"rewards": [
{ "itemId": "gold-coins", "quantity": 500, "type": "IN_GAME_CURRENCY" }
]
}
}- The snapshot reflects state as of the request. On
RegisterPaymentit is pre-purchase; onConfirmPaymentit reflects the XP the confirmed purchase awards. - It is present only when the shop has a published, enabled loyalty program and a live campaign. Otherwise the field is omitted and the request body is byte-identical to a non-loyalty shop's.
- Treat it as read-only context. It never replaces the authoritative loyalty state your backend receives on the loyalty webhooks; grant XP-linked effects from the webhook, not from this snapshot.
The snapshot fields match the shared PlayerLoyaltyState payload documented in Loyalty webhooks. The signed pointsDelta and previousTierId are event-specific and meaningful only on webhooks; the purchase-request snapshot otherwise carries the same fields, including the balance, tier, progression, and nextMilestone. When the purchase's XP gain crossed one or more milestone thresholds, the snapshot also carries unlockedMilestones, with the same semantics and de-duplication guidance as on the webhook payload.
Player API
Retrieve player profile information for personalized experiences.
GetPlayer
GET /api/v1/player: returns player profile data (name, avatar, language, in-game level, in-game currency balances) for display in the web shop. Stash calls this endpoint with the playerId you supplied to the catalog request.
See the GetPlayer API Reference for the full request/response schema.
Player level
Return the player's level in inGameLevel as a positive integer. The web shop can show it on the player's profile, for example as a power level. Omit it when your game has no player level; the web shop then shows none. Stash treats 0 as an omitted level.
In-game currency balances
Return the player's balances in wallet, one entry per in-game currency. Stash reads the first 100 entries and ignores the rest.
| Field | Value |
|---|---|
bonusId | The ID of the reward type configured on the Reward Types surface in Studio. Matched exactly and case-sensitively. Up to 256 characters: letters, digits, ., _ and -. Required; an entry without it is ignored. If two entries share a bonusId, Stash keeps the first. |
bonusType | WALLET_BONUS_TYPE_IN_GAME_CURRENCY. Stash ignores entries of any other type. |
balance | The amount the player owns, a non-negative 64-bit integer, sent as a JSON integer or a decimal string. Required; an entry without it is ignored. Use a string for values above 9007199254740991. 0 is a real balance. A negative, fractional or non-numeric value fails the player lookup. |
Stash attaches the name and icon of the matching reward type in Studio to each balance. Studio supports one in-game currency reward type per shop today, so a balance for a bonusId that is not a reward type in Studio comes back without a name or icon. An entry whose bonusId names a reward type of another kind, such as loyalty points, is ignored. See Reward types.
{
"player": {
"playerId": "player-123",
"name": "PlayerOne",
"wallet": [
{ "bonusId": "gold-coins", "bonusType": "WALLET_BONUS_TYPE_IN_GAME_CURRENCY", "balance": "12000000000000000" }
]
}
}inGameCurrency is deprecated but keeps working, so an existing integration does not need to change now. Stash shows a nonzero value as the balance of the shop's in-game currency reward type in Studio, and omits it when the shop has none. It holds a single currency, and a value above 4294967295 fails the player lookup, so use wallet for new integrations and for large balances. Whenever wallet has at least one entry, Stash uses it for currency balances and ignores inGameCurrency, even if every wallet entry is ignored.
How is this guide?
Real-time Catalog
Learn how to expose REST API endpoints on your game backend to power personalized web shops with real-time offers. This guide covers the Catalog, Player, and Purchase APIs with protobuf-based type-safe integration.
Payment Flow
Understand where the money moves in a Real-time Catalog purchase, why ConfirmPayment is required rather than optional, and which checks only your backend can enforce.