Skip to main content

Subscriptions

Beta

Subscriptions are in beta. The API, payload shapes, and Developer Console UI may change without notice. Contact the Jest team before shipping a subscription to production, and keep an eye on What's new for breaking changes.

The Jest platform allows developers to sell recurring subscriptions to their games using the Payment SDK methods. Subscriptions complement one-off product purchases and are a good fit for things like ad removal, premium tiers, or access to additional content.

As with one-off purchases, Jest handles the entire checkout and recurring billing with the player via popular digital wallets (Apple Pay, Google Wallet, card on file). Your game is responsible for reading the player's current entitlement and unlocking the relevant features.

Subscriptions on Jest
Subscriptions on Jest via digital wallets

How subscriptions differ from products

Unlike one-off product purchases, subscriptions:

  • Are tied to the player's wallet, not a single transaction. The wallet is what's billed on the recurring schedule.
  • Don't require a complete step. The platform manages the billing lifecycle; your game only needs to read the wallet's current entitlement on startup (and after BeginSubscription succeeds) and unlock features accordingly.
  • Are only available to registered users. Guests must register before they can subscribe.
  • Are returned alongside their current Status (active or inactive), so the same response tells your game both what's on offer and what the player already has.

Subscription lifecycle

A subscription represents an ongoing entitlement granted to a wallet for as long as billing succeeds.

  1. On startup, call GetSubscriptions() to read the catalog and the wallet's current entitlement.
  2. Unlock subscription-gated features based on each subscription's Status.
  3. If the player chooses to subscribe to an inactive offering, call BeginSubscription(subscriptionSku).
  4. If checkout succeeds, the SDK returns the now-active subscription. Apply the entitlement in your game.
  5. Cancellations, expirations, and renewals are handled by the platform; the next call to GetSubscriptions() will reflect the updated Status.

Happy path (new subscriber)

Returning subscriber (startup reconciliation)

On every startup, your game should re-read GetSubscriptions() and apply the resulting entitlements. This is how you reflect cancellations, expiries, and renewals that happened while the player was away.

How to use the SDK

Payload shapes

Where the SDK references SubscriptionData, it contains:

public class SubscriptionData
{
public string Sku; // the subscription's SKU, configured in the Developer Console
public string DisplayName; // suitable for display in your game's UI
public string DisplayDescription; // optional short description; null when not set
public decimal Price; // the subscription price in the currency specified in Currency
public string Currency; // the currency (ISO 4217 code) the price is in
public string BillingPeriod; // "weekly", "monthly", or "yearly"
public string Status; // "active" or "inactive"
}

A Status of "active" means the player's wallet currently has the entitlement and should have access to whatever the subscription unlocks. "inactive" means they don't have it (either never subscribed, or it has expired/been cancelled).

Set up subscriptions

Set up and price subscriptions using the Jest Developer Console.

For more information, see Manage subscriptions.

List subscriptions (GetSubscriptions)

To retrieve the subscriptions available to the player along with their current entitlement, call GetSubscriptions.

var payment = JestSDK.Instance.Payment;

try
{
Payment.GetSubscriptionsResponse response = await payment.GetSubscriptions();
foreach (var subscription in response.Subscriptions)
{
if (subscription.Status == "active")
{
UnlockFeaturesFor(subscription.Sku);
}
}
}
catch (Exception ex)
{
Debug.LogError($"Failed to load subscriptions: {ex.Message}");
}

The response contains:

PropertyNote
SubscriptionsThe list of SubscriptionData objects (see Payload shapes).
SignedA signed JWT carrying the same Subscriptions array. See below.
note

For guest players, GetSubscriptions returns an empty Subscriptions list and an empty Signed string. Subscriptions are only available to registered users.

Displaying prices

The way subscription prices are displayed should be based on the Price, Currency, and BillingPeriod. Currency is an ISO 4217 code which can be used to select the correct currency symbol or formatting.

Start a subscription (BeginSubscription)

Call BeginSubscription with the Sku of a subscription returned by GetSubscriptions().

The method returns a SubscriptionResult with one of the following outcomes. In mock mode, you can simulate each outcome using the JestSDK debug menu.

Success

  • Result == "success" with Subscription and SubscriptionSigned populated. Checkout completed successfully. The returned subscription is now "active" for the player's wallet.

Cancellation

  • Result == "cancel" The player closed or abandoned the checkout flow.

Error

  • Result == "error" with Error containing one of:
    • internal_error - A transient error occurred. Your game may retry.
    • invalid_subscription - The requested Sku is not available. Do not retry with the same Sku. If this persists and the subscription configuration appears correct, contact support.
    • already_subscribed - The player's wallet already has an active entitlement for this subscription. Refresh the wallet's state via GetSubscriptions().
    • guest_not_allowed - The player is a guest. Prompt them to register before starting a subscription.

Any other error (for example, a timeout) should be handled by your game and may be retried.

var payment = JestSDK.Instance.Payment;

try
{
var subscriptionsResponse = await payment.GetSubscriptions();
var premium = subscriptionsResponse.Subscriptions
.Find(s => s.Sku == "premium_monthly");

if (premium == null || premium.Status == "active")
{
// Already entitled, or offering not available; nothing to do.
return;
}

Payment.SubscriptionResult result = await payment.BeginSubscription(premium.Sku);

if (result.Result == "cancel")
{
// Handle cancellation with UI feedback
return;
}

if (result.Result == "error")
{
if (result.Error == "guest_not_allowed")
{
// Prompt the player to register, then re-try.
return;
}
if (result.Error == "already_subscribed")
{
// Re-read entitlement state and unlock features.
return;
}
// internal_error / invalid_subscription: handle with UI feedback
return;
}

// result.Result == "success"
var subscription = result.Subscription;
var subscriptionSigned = result.SubscriptionSigned;
UnlockFeaturesFor(subscription.Sku);
}
catch (Exception ex)
{
Debug.LogError($"Subscription failed: {ex.Message}");
}
tip

After a successful BeginSubscription, prefer using the returned Subscription (or, better, SubscriptionSigned) to unlock features immediately. The next GetSubscriptions() call will return the same "active" status.

Cancel a subscription (CancelSubscription)

Call CancelSubscription with the Sku of an active subscription. The platform opens a confirmation dialog with the player; if the player confirms, the subscription is cancelled at the end of the current billing period (the player retains the entitlement until then).

The method returns a CancelSubscriptionResult with one of:

Success

  • Result == "success" The player confirmed the cancellation. The subscription will remain "active" until the end of the current billing period, then transition to "inactive" on the next GetSubscriptions() call.

Cancellation

  • Result == "cancel" The player dismissed the confirmation dialog without cancelling the subscription. No change is made.

Error

  • Result == "error" with Error containing one of:
    • internal_error - A transient error occurred. Your game may retry.
    • not_found - The requested Sku does not correspond to a known subscription. Do not retry with the same Sku.
    • not_active - The player's wallet does not have an active entitlement for this subscription. Refresh state via GetSubscriptions().
    • guest_not_allowed - The player is a guest. Guests cannot have subscriptions to cancel.
var payment = JestSDK.Instance.Payment;

try
{
Payment.CancelSubscriptionResult result = await payment.CancelSubscription("premium_monthly");

if (result.Result == "cancel")
{
// Player dismissed the confirmation dialog; nothing to do.
return;
}

if (result.Result == "error")
{
Debug.LogError($"Cancel failed: {result.Error}");
return;
}

// result.Result == "success"
// The subscription will lapse at the end of the current billing period.
// Re-read GetSubscriptions() the next time you need authoritative state.
}
catch (Exception ex)
{
Debug.LogError($"Cancel failed: {ex.Message}");
}
note

Cancellation only schedules the subscription to lapse at the end of the current billing period. The entitlement remains "active" until then, and your game should continue to unlock the relevant features for the remainder of the period.

Signed subscription data (JWT)

The BeginSubscription and GetSubscriptions methods return subscription data in two forms:

  1. As plain objects (Subscription / Subscriptions) for convenience.
  2. As signed tokens (SubscriptionSigned / Signed) in the form of a signed JSON Web Token (JWT).

The data inside the signed token is equivalent to the plain object. For critical actions such as unlocking paid content or applying entitlements server-side, you must only trust the signed token after verifying its signature.

For server-side verification examples and details, see the HTML5 SDK Subscriptions documentation.

warning

Failure to verify the signed token can leave your game vulnerable to exploitation. Players can otherwise spoof an "active" status without paying.

Testing

When using a sandbox user, the Stripe checkout flow still needs to be completed — the player goes through the same checkout UI as a normal player — but the order total is $0 and no real charge is made. Once checkout is completed, the resulting subscription is treated as "active" for the duration of a normal billing period, so you can test the subscribed and unsubscribed flows end-to-end without spending real money.

In mock mode, you can simulate each BeginSubscription and CancelSubscription outcome (success, cancel, errors) and toggle the wallet's entitlement on each subscription SKU via the JestSDK debug menu.

Revenue estimate

Jest's economics model splits revenue between the publisher, the platform, and publishers who bring users in to the platform.

Subscription revenue is recognized on each successful billing cycle, not at signup. Final revenue will be reported via the developer console (details coming soon).