Refunds & XP Adjustments
How Stash reverses Loyalty XP on refund: proportional clamp-to-zero debit, tier downgrade, and idempotency across partial refunds; plus free-gift XP grants and campaign/season boundary behavior.
Loyalty XP is not static after a purchase. Refunds reverse it, and free gifts can grant it. Both are delivered to your backend as loyalty-carrying webhook events so your game state stays in sync with the player's XP.
Refunds are XP-update events
When a webshop purchase is refunded, Stash does not just drop the loyalty state. It debits the player's Loyalty XP, recomputes the tier, and delivers PURCHASE_REFUNDED with the updated loyalty payload and a negative xpDelta.
Proportional debit
The XP debited is proportional to how much of the original purchase was refunded:
debit = round( earnedXP x refundAmount / originalTotal )earnedXPis the XP the original purchase awarded.- Rounding is half-up.
- A full refund debits the full earned XP; a partial refund debits proportionally.
Clamp to zero
The balance is floored at zero. A debit never drives XP negative, even if the player has already spent XP progression or the balance has otherwise moved since the purchase.
Tier downgrade
After the debit, Stash recomputes the tier from the new balance. If the balance falls below the current tier's threshold, currentTierId reflects the lower tier and previousTierId reports the tier the player just left. React to the downgrade the same way you would any tier change.
Idempotency and partial refunds
- Each refund is applied once. Duplicate refund notifications for the same refund are de-duplicated and do not double-debit.
- Multiple partial refunds on one order each debit proportionally and independently. Because the balance is clamped at zero, a sequence of partials that sums to the full amount can never over-debit.
The XP debit is a real balance change, so its failure handling is stricter than webhook field population. If the debit cannot be applied, Stash still sends PURCHASE_REFUNDED (the money is already refunded) but omits the loyalty payload rather than report an inconsistent balance. Treat a refund with no loyalty object as "XP unchanged for this event".
Free-gift XP
A free gift can grant Loyalty XP in addition to its item. The XP amount is declared on the free gift in your catalog. On redemption, Stash credits the XP to the player and FREE_ITEM_REDEEMED carries the loyalty payload with a positive xpDelta.
- If a free gift declares no XP reward, no XP is credited and the event carries no
loyaltypayload; it serializes exactly as before. - Any metadata declared on the free gift is passed through to the
metadatafield on the webhook, the same way product metadata is. Use it for opaque correlation keys, not for display or business logic.
See the free item redeemed example.
Campaign and season boundaries
Loyalty state is keyed by campaign: a generation of the program scoped to a season. Every loyalty payload carries the campaignId it belongs to.
The XP wallet is a single running balance, not a per-campaign balance. This matters when a refund lands after a new season has started:
- The debit still applies to the one XP wallet.
- The
earnedXPused for the proportional debit comes from the original purchase, so the math is stable across the boundary. - The reported
currentTierIdandcampaignIdreflect the current campaign's tiers, not the campaign the purchase was made in.
Scope any per-season reconciliation on your side to the campaignId on each event, and expect a late refund to be reported against the live campaign.
How is this guide?
Loyalty Webhooks
The shared loyalty webhook payload and the four events that carry it: field reference, per-event JSON examples, when the payload is present, and receiver guidance for consuming loyalty state.
About Stash Launcher
Discover Stash Launcher - a customizable direct-to-consumer distribution platform for delivering PC games directly to players on Windows and macOS. Experience features like custom branding, incremental updates, in-game purchases, community engagement tools, and seamless management through Stash Studio or CLI automation.