# Integrate a webshop into a Unity game

> Open a webshop from a Unity game with an authenticated player session.

Open a webshop from your Unity game so players can buy products from your In-App Purchasing (IAP) catalog.

Webshops use the IAP catalog and payment providers that you already configured. Players can complete purchases on the web without a separate in-game storefront.

To open the shop, add an in-game action, such as a button, that opens the shop URL with the player’s authenticated session attached. The session identifies the player to the webshop.

## Prerequisites

Before you start, make sure you meet the following prerequisites:

* A Unity project that integrates [In-App Purchasing](/iap.md).
* The [Authentication](/authentication.md) SDK (version 3.7.1 or later) in your project.
* A webshop in the target environment.

To open the live shop, publish the webshop so it’s available at `shop.unity.com/{studio}/game/{slug}`. To test a draft, you only need a created webshop in the target environment.

For more information, refer to [Create and publish your first webshop](./create-and-publish-your-first-webshop.md).

## Open the webshop from your game

### Use the SDK helper

The Unity IAP SDK provides `RedirectToWebshop` to open your webshop with the required parameters and compliance checks.

Call `RedirectToWebshop` through the `IPaymentProvidersExtendedPurchaseService` for the payment provider. Leave `catalogListingId` empty to open the webshop front page, or pass a listing ID to open a product page directly:

```csharp
// Open the front page
 UnityIAPServices.StoreController(PaymentProvider.Name).PaymentProvidersExtendedPurchaseService.RedirectToWebshop();

// Open a specific product page
 UnityIAPServices.StoreController(PaymentProvider.Name).PaymentProvidersExtendedPurchaseService.RedirectToWebshop(catalogListingId: "your-listing-id");
```

#### Require compliance approval before opening

To gate the webshop on a compliance check, register a callback with `SetComplianceCheck` before calling `RedirectToWebshop`. The callback runs before each redirect. Returning `false` cancels the redirect and fails the purchase with `PurchasingUnavailable`:

```csharp
StoreController(PaymentProvider.Name).PaymentProvidersExtendedPurchaseService
    .SetComplianceCheck(async context => await ShowComplianceDialog(context));
```

#### Control how the webshop opens

By default, the SDK opens the webshop in an external browser. To change this, call `SetWebshopPresentationMode(CheckoutPresentationMode)` on the `IPaymentProvidersExtendedPurchaseService` for the payment provider before redirecting. This setting is independent of `SetCheckoutPresentationMode`.

### Manual integration

If you can't use the SDK helper, construct and open the webshop URL directly.

To open a webshop from a button or other in-game action, resolve the shop URL from the Webshop service, then open the URL with `Application.OpenURL`.

Resolving the URL at runtime lets the same build open the published shop or an environment draft preview. This lets you test a draft before you publish.

To resolve the URL, send a `GET` request to the `storefront-link` endpoint:

```text
https://webshop.services.api.unity.com/v1/projects/{projectId}/environments/{environmentId}/storefront-link
```

By default, the service returns a URL automatically based on the environment state:

* A published production environment returns the live public storefront URL. The live URL resolves anonymously.
* A non-production environment or an unpublished production environment returns a short-lived draft preview URL.

To preview unpublished changes to a live shop, add the `source=draft` query parameter. This returns the draft preview URL, even for a published production environment:

```text
https://webshop.services.api.unity.com/v1/projects/{projectId}/environments/{environmentId}/storefront-link?source=draft
```

The integration uses the following Authentication SDK tokens:

* The access token authorizes the `storefront-link` request when you resolve a draft preview. Send the access token in the `Authorization: Bearer` header.
* The session token authenticates the player in the browser. Mint a short-lived, single-use restricted token with `GenerateRestrictedTokenAsync` right before you open the shop, then append its session token to the resolved URL as the `sessionToken` query parameter.

After you resolve the URL, append the session token, project ID, and environment so the shop opens for the correct player.

For more information about each query parameter and how the shop behaves when parameters are missing, refer to [Open a webshop from your game](./deep-links.md#outbound-links).

```csharp
using System;
using System.Collections;
using System.Collections.Generic;
using Unity.Services.Authentication;
using UnityEngine;
using UnityEngine.Networking;

public class WebshopLauncher : MonoBehaviour
{
    // Replace these with the values from your Unity Cloud project and webshop configuration.
    const string ProjectId       = "<your-project-id>";
    const string EnvironmentId   = "<your-environment-id>"; // used in the API request path
    const string EnvironmentName = "production";            // used in the shop URL

    const string StorefrontLinkEndpoint =
        "https://webshop.services.api.unity.com/v1/projects/{0}/environments/{1}/storefront-link";

    // draftPreview: request source=draft to preview unpublished changes even on a live env.
    public void OpenShop(string locale, string currency, bool draftPreview = false)
    {
        StartCoroutine(OpenShopRoutine(locale, currency, draftPreview));
    }

    IEnumerator OpenShopRoutine(string locale, string currency, bool draftPreview)
    {
        var signedIn = AuthenticationService.Instance.IsSignedIn;

        // The access token is required for draft previews; live storefronts open anonymously
        // and ignore any token sent. Send it whenever the player is signed in.
        if (draftPreview && !signedIn)
        {
            Debug.LogWarning("WebshopLauncher: draft preview requires the player to be signed in.");
            yield break;
        }

        // 1. Ask the Webshop service for the storefront URL.
        var endpoint = string.Format(StorefrontLinkEndpoint, ProjectId, EnvironmentId);
        if (draftPreview)
            endpoint += "?source=draft";

        using var request = UnityWebRequest.Get(endpoint);
        request.SetRequestHeader("Accept", "application/json");
        if (signedIn)
            request.SetRequestHeader("Authorization", $"Bearer {AuthenticationService.Instance.AccessToken}");

        yield return request.SendWebRequest();

        if (request.result != UnityWebRequest.Result.Success)
        {
            Debug.LogError($"WebshopLauncher: storefront-link request failed " +
                           $"({request.responseCode}): {request.error}");
            yield break;
        }

        var link = JsonUtility.FromJson<StorefrontLinkResponse>(request.downloadHandler.text);
        if (link == null || string.IsNullOrEmpty(link.storefrontUrl))
        {
            Debug.LogError("WebshopLauncher: storefront-link response did not contain a storefrontUrl.");
            yield break;
        }

        // 2. Mint a short-lived, single-use restricted token for the webshop redirect.
        var tokenOptions = new RestrictedTokenOptions
        {
            Services   = new List<string> { "no-svc" }, // ID token unusable against any real service
            SingleUse  = true,                          // consumed by the webshop on first refresh
            TtlSeconds = 60,                            // minted right before redirect
        };

        var tokenTask = AuthenticationService.Instance.GenerateRestrictedTokenAsync(tokenOptions);
        yield return new WaitUntil(() => tokenTask.IsCompleted);

        if (tokenTask.IsFaulted)
        {
            Debug.LogError($"WebshopLauncher: failed to generate restricted token: {tokenTask.Exception}");
            yield break;
        }

        // 3. Open the shop.
        var sessionToken = tokenTask.Result.SessionToken;
        var shopUrl = BuildShopUrl(link.storefrontUrl, sessionToken, locale, currency);

        Application.OpenURL(shopUrl);
    }

    static string BuildShopUrl(string storefrontUrl, string sessionToken, string locale, string currency)
    {
        var url = storefrontUrl;
        url = AppendParam(url, "sessionToken", sessionToken);
        url = AppendParam(url, "projectId", ProjectId);
        url = AppendParam(url, "environment", EnvironmentName);
        url = AppendParam(url, "locale", locale);
        url = AppendParam(url, "currency", currency);
        return url;
    }

    static string AppendParam(string url, string key, string value)
    {
        if (string.IsNullOrEmpty(value))
            return url;

        var separator = url.Contains("?") ? '&' : '?';
        return $"{url}{separator}{key}={UnityWebRequest.EscapeURL(value)}";
    }

    [Serializable]
    class StorefrontLinkResponse
    {
        public string storefrontUrl;
        public bool live;
    }
}
```

After the shop opens, it removes these parameters from the URL and persists the player's session in the browser. For more information about sessions and persistence, refer to [Open a webshop from your game](./deep-links.md#authenticate-players-and-persist-sessions).

> **Warning:**
>
> Child Privacy: Child data laws, including but not limited to the Children's Online Privacy Protection Act ([COPPA](https://www.ftc.gov/enforcement/rules/rulemaking-regulatory-reform-proceedings/childrens-online-privacy-protection-rule)), impose restrictions on how data can be collected and used from age-restricted users (for example, children under the age of 13, 16, or 18 depending on the applicable laws). You will not transmit to Unity any “Personal Information” belonging to an age-restricted user unless in compliance with applicable laws as outlined in our [Unity Terms of Service](https://unity.com/legal/terms-of-service).

If you don't have locale or currency to pass, then omit those parameters. By default, the IAP catalog uses the player’s browser locale and defaults to US dollars (USD). For more information about catalog locale handling, refer to [Catalog and payments in webshops](./catalog-and-payments.md).

## Handle inbound deep links

When you set a **Deeplink URL** for the webshop, the shop returns the player to your game through that custom URL scheme. The shop uses the return deep link after a purchase and when a player selects **Connect to game** on the unauthenticated landing page.

To receive return deep links, register the custom URL scheme on the device and handle incoming links at runtime. For more information about how the shop builds the return URL, refer to [Open a webshop from your game](./deep-links.md#post-purchase-return-to-the-game).

> **Note:**
>
> For the **Connect to game** flow, the Unity IAP SDK automatically reopens the webshop. You don't need to handle that link yourself.
>
> Make sure the following requirements are met:
>
> * The player signs in to Unity Authentication before you initialize IAP.
> * The PaymentProvider store is connected.
>
> Handling the post-purchase return uses standard platform custom URL scheme handling and doesn't require a Unity-specific SDK.

> **Note:**
>
> Return deep links resolve only in a built player — not in the Unity Editor or WebGL. The Editor isn't registered as a handler for your custom scheme, so a link opened from the browser never reaches Play mode, and WebGL builds use their page URL rather than a custom scheme. Test the return flow on a device (iOS or Android) or a standalone build. For platform support and registration details, refer to Unity's [Deep linking](https://docs.unity3d.com/Manual/deep-linking.html) manual.

### Register the URL scheme

Declare the same scheme that you set in the **Deeplink URL** field in the Dashboard. The operating system uses the scheme to route the link to your game.

* **iOS and macOS**: Add the scheme under **Edit > Project Settings > Player > Other Settings > Supported URL schemes**. Unity writes it into the built app's `Info.plist` (`CFBundleURLTypes`) at build time. Prefer this over hand-editing the generated `Info.plist`, which is regenerated on every build.
* **Android**: Add an `intent-filter` with a `<data android:scheme="mygame" />` entry to your activity, through a custom main manifest or the Gradle manifest template.

The scheme must match the **Deeplink URL** value exactly. After you change an Android manifest, reinstall the game so the operating system picks up the new scheme.

### Handle the link at runtime

Subscribe to `Application.deepLinkActivated` for links that arrive while the game is running. At startup, also check `Application.absoluteURL` to handle cold starts, where the deep link launches the game.

```csharp
void Awake()
{
    // Links that arrive while the game is running.
    Application.deepLinkActivated += OnReturnFromWebshop;

    // Cold start: the deep link launched the game.
    if (!string.IsNullOrEmpty(Application.absoluteURL))
        OnReturnFromWebshop(Application.absoluteURL);
}

void OnReturnFromWebshop(string url)
{
    // Handle the post-purchase return: the shop appends ?status=success
    // (and playerId when available) after a completed purchase.
    if (new Uri(url).Query.Contains("status=success"))
    {
        // Purchase completed on the web — refresh the player's entitlements.
    }
    // The Connect to game sign-in link is reopened by the SDK automatically,
    // so it needs no handling here.
}
```

The shop only sends `status=success`, so handle that case here. The SDK automatically reopens the **Connect to game** sign-in link, as described in the previous note.

## Test opening the shop

Test the draft preview before you publish, then repeat the test against the live shop. To test a draft, call `OpenShop` with `draftPreview: true` and confirm the player is signed in. You can target a non-production environment, or use `source=draft` to preview unpublished changes on your production environment.

To verify the integration, follow these steps:

1. Build and install your game on a mobile device.
2. Trigger the in-game button that calls `OpenShop` to launch the device's system browser.
3. Confirm the browser opens to the resolved storefront URL with your locale and currency in the URL. A draft opens an environment-scoped preview URL. The live shop opens `shop.unity.com/{studio}/game/{slug}`.
4. Confirm the shop opens to the authenticated product list rather than the unauthenticated landing page.
5. Complete a sandbox purchase. Refer to the relevant IAP payment provider's sandbox documentation for test credentials.
6. When the draft works as expected, call `OpenShop` with `draftPreview: false` and repeat the test against the live published shop.

If the shop opens to the unauthenticated landing page instead of the product list, confirm your game passed a valid `sessionToken` and `projectId` on the URL. Refer to [Troubleshooting webshops](./troubleshooting.md).
