# Webshops

Sell in-game items through a fully Nexus-managed webshop.

A Nexus webshop is a fully managed storefront that sells your in-game items. To set one up you provide some product details, build two publisher API routes (player lookup and entitlement), optionally configure FastSpring if it is your merchant of record, and optionally define purchase limits.

## General setup

Nexus webshops are fully managed by Nexus itself, requiring no work from you. However, to get started, we'll need the following information:

* **Webshop Styling:** Nexus will work with you on styling your webshop to your specifications
* **SKU Titles:** The publicly displayed titles for each SKU you'd like to sell on your webshop
* **Price:** Price of each SKU in USD
* **Short Description:** A short description of each SKU
* **Bonus/Discount:** Any bonus or discount in comparison to the normal in-app SKU for each SKU
* **Image Asset:** An image asset representing each SKU
  * Image assets should all have a consistent aspect ratio

## Required APIs

To run a webshop through Nexus you'll need to create two API routes:

### Player lookup

**PlayerLookupUrl:** GET \<url>/\{`playerId`\}

Example: `https://<your-api-url>/player/{playerId}`

The player lookup URL is used to verify the player when they enter their `playerId` on the webshop in order to checkout. It is a GET call that includes the entered `playerId` as a URL parameter. A valid response should be a `200 OK` code with a JSON body containing `playerId`, and `playerName` (display name). A non-`200` response will be considered an invalid `playerId`.

The response is a JSON body that looks something like this:

```json
{
  "playerId": "tMHDWGdh53DiiXaCKa9U",
  "playerName": "Test Player"
}
```

#### Custom player attributes

By default, a player is only prompted for their `playerId` during sign in. In the event that additional information is required, custom player attributes may be configured. These can be an open text input or a select box with static options. The values of these attributes will be included as query parameters on the player lookup URL, but they can also be included in the path as a template variable if needed. They will also be included in the entitlement webhook event.

If additional attributes are needed for your webshop's player lookup, provide us the following information for each attribute:

* name
* label
* type (text input or select box)
* select box options, if applicable
* tooltip
* whether this attribute is required or optional

### Entitlement

**EntitlementWebhookUrl:** POST \<url>

Example: `https://<your-api-url>/webhook/purchase`

This is the URL that is hit upon a successful purchase, containing JSON about the purchase. The lookup response uses `playerName`, while the entitlement event uses `playerDisplayName`; these are two different payloads.

```jsonc
{
  "eventId": "1_63OfIMngN5aqHEUixc_",
  "createdAt": "2024-10-25T19:49:05",
  "eventType": "Webshop",
  "eventAction": "WebshopPurchase",
  "event": {
    "creatorCode": {
      "code": "popcornfrog",
      "memberId": "0Con-WN07cuBMdEkr56oo", // Nexus member ID
      "playerId": "2ixlNNFdJh-9scoXek80w" // Your internal ID for this member
    },
    "gifting": {
      "isGift": true,
      "fromPlayerId": "_mNHxEkH", // PlayerId of the user that made the purchase
      "fromPlayerName": "John" // Optional
    },
    "items": [
      {
        "skuId": "12yKNG5CAZOrr94aBrPsg",
        "total": 99.99, // Not including taxes
        "itemId": "8000-gems-unique-id",
        "quantity": 1,
        "subtotal": 99.99 // Not including taxes
      }
    ],
    "total": 108.86, // Includes taxes
    "country": "US", // Purchaser's country
    "orderId": "C9j2933DQS8fFXUor0e-d",
    "currency": "USD",
    "orderRef": "NEX241025-4862-22109",
    "playerDisplayName": "Test User", // Optional. We will not always be able to collect a display name
    "playerId": "testuser", // If gifting.isGift is true, this will be the playerId of the gift recipient
    "playerAttributes": { // Included only if custom player attributes are configured
      "serverId": "server1"
    },
    "subtotal": 99.99, // Not including taxes
    "playerEmail": "player@example.com",
    "transactionDate": "2024-10-25T19:48:35.676Z",
    "attributionGroupId": "-XgND9kJQRre_UzlaptAE",
    "attributionGroupName": "Great Game"
  }
}
```

<Aside type="caution">
This event should be verified. Please see these [instructions on how to verify this event](/additional-features/webhooks/#verifying-a-notification).
</Aside>

## FastSpring setup

<Aside type="note">
This section is only required if you are already using FastSpring as your merchant of record.
</Aside>

### Encryption

Encryption is required to build the FastSpring cart. This is set up under Developer Tools -> Store Builder Library. If you already have encryption setup within FastSpring, you will need to contact your account manager to set up an additional account that can be used specifically for a Nexus webshop.

This page provides an Access Key and a file upload option. The Access Key will need to be shared with Nexus.

A file should be uploaded that contains the following (public) certificate:

```text
-----BEGIN CERTIFICATE-----
MIIDczCCAlugAwIBAgIUcTNeL15TSBYew+KPV6Z5c5inE6cwDQYJKoZIhvcNAQEL
BQAwYjELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVRleGFzMQ8wDQYDVQQHDAZBdXN0
aW4xEzARBgNVBAoMCkNocm9ubyBJbmMxHTAbBgkqhkiG9w0BCQEWDnJ5YW5AY2hy
b25vLmdnMB4XDTI0MDcxNjE3NTYzM1oXDTI0MDgxNTE3NTYzM1owYjELMAkGA1UE
BhMCVVMxDjAMBgNVBAgMBVRleGFzMQ8wDQYDVQQHDAZBdXN0aW4xEzARBgNVBAoM
CkNocm9ubyBJbmMxHTAbBgkqhkiG9w0BCQEWDnJ5YW5AY2hyb25vLmdnMIIBIjAN
BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjWKHeqV/s6/gDEokih7qp3ky7ONy
xWhifnl8COGmG6BAIWOOWkW2GKsZXUl6RHJC/CU9Dk03gYSCDtP0gu/hPlGUgxAr
kHhIoTJWsNZP/bsEhS/ozlekatL3Q2Lbu4Rb6Zn/RwbDc52ISGfHe4HdQvEfD28Q
PYaCyfvK1gHezmETXqHLj+BQicOhe/IT++v+fD+0QVnA9rCy4RS4WPakEtE4abwz
YB7IqCaByyvhiMLWJ+nVWCZUfUCyWKlJY0eUuXnjB5izBGobUHYI6f226C2DGQqV
WX5c3pVhcA4j/c3hPWbal8XLyxgTkQ4yGOErntbd/RObQK+yNuFo0Or0/QIDAQAB
oyEwHzAdBgNVHQ4EFgQUUmU8kCCEDYgfH7d9qYfwQlWkGPgwDQYJKoZIhvcNAQEL
BQADggEBAB4Ouv3emB6yZX+JQDnyCdXOBAB1gV7ibSbeYDHSFzfDmJnvOUwr2qoN
2WDNtdoMi9KyPtDs0JRAn0JAfyE57tnFvNigk0lqosfJ6Z5DncntsXUTdxTHWDRC
lAac1BMWlei6+FReFhdfAuCmgMytAy8i+mBrIxyikE3tGwOZHh0zykCnyZUxHfbC
nML81EID8B99C9WlJmS0G02d2Gy6jD6yFNxHPKMnSP0BMUchQaRX20zF46AuasoY
t0b00PlIaeanfd/zxK5n161pJZYKtF+pst9KXp3bsNK/wg1WuKzspMzVDMFeZo3A
6k+fxmqktqD+v9gO9rvn0frWN/kSuy4=
-----END CERTIFICATE-----
```

### Create a storefront

A new Embedded Storefront will need to be created under Storefronts -> Embedded Storefronts -> Create Embedded Storefront. Once a new Embedded Storefront is created, the following settings should be changed from their default:

* **Override Store Currencies:** If you accept currencies other than USD, you will need to select the Override Store Currencies option and deselect all currencies except USD. This can be done under More -> Languages and Currencies.
* **'Promotional Code' Field:** This option should be disabled. This can be done under Settings -> Promotion.
* **Display Newsletter Subscription Checkbox:** This option should be disabled. This can be done under Settings -> Customer Information.
* **Styling:** We will work with you to style your embedded storefront so that it matches the styling of your Nexus webshop.
* **Whitelisted Websites:** Add the following URLs to the Whitelisted Websites. This can be done by clicking the red "No whitelisted websites" item.
  * [https://nexus.gg](https://nexus.gg)
  * [https://www.nexus.gg](https://www.nexus.gg)
  * [http://nexus.gg](http://nexus.gg)
  * [http://www.nexus.gg](http://www.nexus.gg)

Once your settings have been updated, click the "Place on your website" button and copy the `data-storefront` URL from the script. This URL will need to be shared with Nexus.

<Aside type="caution">
Your Embedded Storefront will need to be switched to "Online" to be able to accept live purchases.
</Aside>

### Adding products

Nexus will handle adding and managing products in your webshop (see [General setup](/additional-features/webshops/#general-setup)), but two placeholder products need to be added to your FastSpring account. These can be added under Products -> Create Product:

* **Product 1**
  * Product Title: nexus-webshop-product
  * Product Path: nexus-webshop-product
  * Tax Category: DG010301
    * This category can be selected in the Product Tax Category modal under Games -> Online
* **Product 2**
  * Product Title: nexus-webshop-tip
  * Product Path: nexus-webshop-tip
  * Tax Category: NT9999999
    * You will need to contact your FastSpring account manager to set this tax category

### Webhooks

Finally, you will need to setup two webhooks. This can be done under Developer Tools -> Webhooks -> Configuration -> Add Webhook:

* **Webhook 1**
  * Title: Test Orders
  * Get webhooks from: Test Orders
  * Check "Enable webhook expansion"
* **Webhook 2**
  * Title: Live Orders
  * Get webhooks from: Live Orders
  * Check "Enable webhook expansion"

For each webhook, add a URL endpoint by clicking the "Add URL Endpoint" button:

* **Test Orders Webhook**
  * URL: `https://api.nexus-dev.gg/v1/webhook/<nexus_id>/fastspring`
    * Nexus will provide you with your `nexus_id`
  * HMAC SHA256 Secret: You will need to specify a HMAC SHA256 secret and share this with Nexus
  * Events: Check `payoutEntity.created`
* **Live Orders Webhook**
  * URL: `https://api.nexus.gg/v1/webhook/<nexus_id>/fastspring`
    * Nexus will provide you with your `nexus_id`
  * HMAC SHA256 Secret: You will need to specify a HMAC SHA256 secret and share this with Nexus
  * Events: Check `payoutEntity.created`

## Purchase limits

Purchase limits are used to restrict the number of times a player may purchase a particular item over a period of time.

### Nexus managed

Nexus can manage purchase limits entirely if desired, but these limits will only consider purchases from the webshop (in-game purchases will not be included). To use Nexus managed purchase limits, you will need to inform us of the number of purchases and the interval (daily, weekly, monthly, lifetime). You may also supply a note that will be displayed on an item.

* Daily: Resets at 12 am UTC each day
* Weekly: Resets at 12 am UTC on Sunday of each week
* Monthly: Resets at 12 am UTC on the first day of each month
* Lifetime: The purchase limit does not reset

### API routes

In-game transactions may be included in the purchase limits by providing this information through a public API route. This can be included in the [**PlayerLookupUrl**](/additional-features/webshops/#player-lookup) response, or can be a separate API call.

The purchase limits response should be a JSON array of **PurchaseLimitItems**, where each item has the following shape:

```jsonc
{
    "itemId": "string", // Your internal item identifier, that would be used in entitlement
    "limit": "number", // Number of allowed purchases
    "numPurchased": "number", // Number of purchases so far in the current interval
    "note": "string", // Optional. Note displayed on each item
    "resetDate": "Date" // Optional. Date that the interval will reset
}
```

Nexus only requires the itemId, limit, and current number purchased.  This allows your Purchase Limit logic to be as complex as needed, without Nexus needing to understand it, while using the note and resetDate fields to communicate these limits to your players in a succinct manner.

#### PlayerLookupUrl

When using the player lookup route, include a `purchaseLimits` property in the response.

```json
{
    "playerId": "tMHDWGdh53DiiXaCKa9U",
    "playerName": "Test Player",
    "purchaseLimits": [
        {
            "itemId": "200-currency",
            "numPurchased": 0,
            "limit": "2",
            "resetDate": "2026-03-03T00:00:00.000Z",
            "note": "2 Purchases Daily"
        },
        {
            "itemId": "add-free-unlock",
            "numPurchased": 1,
            "limit": "1",
            "note": "Once per account"
        }
    ]
}
```

#### Stand-alone API route

**PurchaseLimitsURL:** GET \<url>/\{`playerId`\}

Example: `https://<your-api-url>/purchase-limits/{playerId}`

The response is an array of purchase limit records, directly in the body.

```json
[
    {
        "itemId": "200-currency",
        "numPurchased": 0,
        "limit": "2",
        "resetDate": "2026-03-03T00:00:00.000Z",
        "note": "2 Purchases Daily"
    },
    {
        "itemId": "add-free-unlock",
        "numPurchased": 1,
        "limit": "1",
        "note": "Once per account"
    }
]
```

#### Check purchase limits upon entitlement

Regardless of the type of Purchase Limits you implement, we recommend checking the limit for a `playerId` during your [entitlement](/additional-features/webshops/#entitlement) workflow. We take what precautions we can and will call the playerLookup or the provided API route on page load, sign in, when building the fastspring cart, and again when processing a fastspring purchase. However, with enough persistence, there is always a chance that a dedicated player could manage to get something through.

<Aside type="caution">
If you encounter an entitlement webhook message that exceeds the purchase limit for a player, do not apply it to their account and return a `406 Not Acceptable` response code. This will notify us of the issue and we can process a refund to the player and look into how it occurred.
</Aside>
