RFC 0011 - Extension Entitlement Service

Summary

To lower the development time and ongoing costs related to maintaining a Bits-enabled Extension, Twitch is proposing a Twitch-built and managed set of APIs that will be available to any Extension that is Bits-enabled.

This proposal has three main goals:

  1. Allow Extension developers to more accurately model their product catalog.
  2. Remove errors in message passing, giving a greater confidence for all parties involved that users are properly getting the benefits of their digital goods.
  3. Reduce the load and complexity of Extension Backend Services for all Extension developers.

Definitions

Product

A product is an offer for digital goods that users can acquire by consuming Bits. This relationship is many-to-many with benefits.

Benefit

A benefit is the digital good that the user acquires as the end result of a Bits transaction.

Entitlement

An entitlement is the linking of a benefit to a user.

Motivation

Currently, providing long-term benefits to a user who has used Bits in an Extension requires the developer to maintain that benefit status in their Extension Backend Service (EBS). This process requires a message to be passed from Twitch, to the Extension front end, and then to the EBS, which means there are multiple points of failure in the case of communication outages, or general server unavailability. This RFC proposes a new set of Twitch APIs that would allow Extension developers to formally define the benefits provided by their products, check user entitlements to those benefits, and finally consume those entitlements, marking them as complete.

Detailed Design

Current System

Product Management

Today, Extension developers use the Developer Rig to define their Extension product catalog, which consists of products defined by a unique SKU, a cost in Bits, and various other metadata fields. The exact benefits conferred by acquiring each product is mapped only in the Extension itself, requiring developers to carefully ensure that each benefit they wish to make available for Bits is tied to a product in the catalog, and that they properly fulfill those benefits on each Bits-in-Extension transaction.

Benefit Lifecycle

Twitch treats all current Bits-in-Extension transactions as instant-use consumables, generating a transaction receipt and firing that receipt off to the purchasing user’s Extension front end. If an Extension developer decides that the transaction should confer a permanent benefit, or provide the ability to exchange that transaction for a temporary benefit at a future time (such as a health pack or 20 minute powerup), that is entirely up to the developer to store and fulfill. Once the receipt has left Twitch, there is no way to replay that message or view a history of past transactions for that user or Extension.

Additional Reading

Users can read more about the current state of Bits-in-Extensions in the developer documentation.

Proposed Changes

Changes to catalog management

Extension developers now have the ability to define benefits, which users automatically get entitled to once they acquire a product that is linked to those benefits. There will be two types of benefits you can define; Consumable and Persistent.

Consumable entitlements are meant to represent items that can be acquired and later used, and each product can entitle different quantities.

Persistent entitlements are meant to be things the user possesses for an unlimited duration, and are only purchaseable once.

Additionally each benefit has a scope which defines where the entitlement will be available. This can either be Channel or Extension. Benefits scoped to Channel will only be availible to be viewed and consumed in the Channel they were acquired in. Benefits scoped to Extension will be available in any channel on Twitch where the Extension is installed.

Each benefit has the following metadata fields:

Field Name Value Type Explanation
Id String The unique identifier for this entitlement. This will be what the users become entitled to in the system, and how your Extension references to the entitlement.
Type Enum Either Consumable or Persistent.
Scope Enum Either Channel or Extension.

Defining a benefit

To define a benefit, developers should use the Developer Rig and navigate to the Manage Products tab, which will contain a new section for defining benefits. There, they can enter a unique identifier, and choose a type for their benefit. Once these are saved, they can’t be modified or removed, but they can be deprecated once they are no longer attached to any product. For historical reasons, we advise that you don’t reuse benefit definitions for different benefits later, as this will make it harder to determine what benefits a user actually has.

Attaching benefits to products

Once you’ve defined the Id and Type of the benefit, its time to attach it to one or more of your existing products. Again, using the Developer Rig, navigate to the Manage Products tab, and use the new fields on the product management area to attach one or more benefits to your products. This is a many-to-many relationship, meaning you could add 10 of a consumable benefit to one product, and 100 of that same consumable to another, or attach multiple different benefits to one product.

Removing a benefit from a product

By removing a benefit, you remove the user’s ability to be entitled to that benefit when acquiring that product. Note that this does not remove the entitlement from users who have already acquired the product, it only prevents future users from being entitled. To remove a benefit, navigate to the Manage Products tab of the Developer Rig and click remove on the benefit next to the product you wish to remove it from.

New entitlement consumption strategy

As a part of defining benefits for a user, we also provide the ability for users to use consumables they are entitled to. We call this process “Consuming” throughout the document. There are three basic steps to a consumption:

  1. Initiation - This is when either the Extension front end or backend makes a request to consume an entitlement that the user possesses. Twitch will confirm that the user has the requisite quantity of consumables, mark them as “consumed” and generate a JWT confirming that the consumption has initiated.
  2. Fulfillment - The EBS will then need to give the user the benefit associated with the entitlement.
  3. Confirmation - To ensure that there are no lost messages, Extensions will be required to confirm a consumption by making a post request to a Twitch endpoint marking the consumption as fulfilled. Not doing this will cause the consumed entitlements to be re-entitled to the user after 5 minutes has passed, ensuring that the user does not lose their entitlements without getting their benefit.

New Websub notifications

To combat errors in message passing between Twitch and the developer’s backend, Twitch will be using a new Websub topic to deliver Bits and entitlement notifications. To subscribe to notifications, the developer should follow the current subscription guide with the new topics listed below.

Bits transactions topic

In addition to sending the existing transaction notifications to the front end of an Extension, we will create a new Websub topic that will notify on Bits transactions in an Extension. This will allow developers to subscribe their backend directly to Twitch without having to route all of their messages through the front end of their Extension. Developers will subscribe to the new topic.

https://api.twitch.tv/helix/extensions/transactions?extension_id=<extension_id>

Where <extension_id> is the developer’s Extension ID. This webhook requires the developers OAuth token to subscribe.

Upon a Bits-in-Extension transaction, the subscribed URL will receive a callback with the following payload.

{
   "data":
      [{
         "user_id": "99526743",
         "user_name": "ampt",
         "transaction_id": "e5a4c7ad-5a68-4e5f-8bcf-8a7256d565e4"
         "transaction_receipt": "<Transaction JWT>",
         "transaction_type": "bits_transaction"
      }]
}

Where <Transaction JWT> decodes to the following object.

Header
{
  "alg": "ES256",
  "typ": "JWT"
}
Payload
{
	"topic": "bits_transaction_receipt",
	"expires": "1530000000", //Set to time of secret expiration
	"data": {
		"transactionId": "e5a4c7ad-5a68-4e5f-8bcf-8a7256d565e4",
		"time":          "1520000000",
		"userId":        "99526743",
		"product": {
			"domainId" : "twitch.ext.krim1spt8mymy9g0dq9zb3ka4qj7h3",
			"sku" : "sample_sku_1",
			"displayName": "Example Product"
			"cost": {
				"amount": "1000"
				"type": "bits"
			}
		}
	}
}
Signature
ECDSASHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload)
)

Entitlement consumption topic

A new topic will be added for consumption notifications, that will send out notifications upon entitlement consumption requests. Much like the Websub topic above, this will allow developers to subscribe directly to Twitch and receive notifications relating to the consumptions of their Extension’s digital goods. Developers will subscribe to the new topic.

https://api.twitch.tv/helix/extensions/consumptions?extension_id=<extension_id>

Where <extension_id> is the developer’s Extension ID. This webhook requires the developer’s OAuth token to subscribe.

Upon a request to consume a user’s entitlement, the subscribed URL will receive a callback with the following payload.

{
   "data":
      [{
         "user_id": "99526743",
         "user_name": "ampt",
         "transaction_id": "7c916756-7aa9-4749-add4-ee754338763b"
         "transaction_receipt": "<Transaction JWT>",
         "transaction_type": "entitlement_consumption"
      }]
}

Where <Transaction JWT> decodes to the following object

Header
{
  "alg": "ES256",
  "typ": "JWT"
}
Payload
{
	"topic": "extension_consumption_request",
	"expires": "1520000280", // Set to time of transaction + 5 minutes
	"data": {
		"transactionId": "7c916756-7aa9-4749-add4-ee754338763b",
		"time":          "1520000000",
		"userId":        "99526743",
		"product": {
			"domainId" : "twitch.ext.krim1spt8mymy9g0dq9zb3ka4qj7h3",
			"sku" : "sample_sku_1",
			"displayName": "Example Product"
			"cost": {
				"amount": "1000"
				"type": "bits"
			}
		},
		"quantity": 3,
		"channel": "23829576"
	}
}
Signature
ECDSASHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload)
)

New entitlement APIs

For interacting with and managing entitlements, we propose a new set of APIs that allow developers to list, check, and consume entitlements, as well as marking entitlements as fulfilled for a given transaction from both the front end, via the JavaScript Extension helper, and the EBS, via new JSON endpoints.

Listing entitlements

Developers will likely want to list entitlements when a user loads an Extension, as this will allow them to build the state of the user’s benefits with one request, rather than checking each possible entitlement individually.

Front end

Promise<Entitlement[]> Twitch.ext.bits.getEntitlements()

This function returns a promise which resolves to an array of entitlements for the current user, scoped to those defined in this Extension domain. Entitlements are only returned if the Extension is configured for Bits.

The Extension helper will have a new method added, getEntitlements(), which will list the entitlements for the logged in user. The response will look like:

{
	"entitlements": [{
	   // Consumable
		"domain": "twitch.ext.krim1spt8mymy9g0dq9zb3ka4qj7h3",
		"id": "game_token",
		"quantity": 32
	},
	{
		// Persistent
		"domain": "twitch.ext.krim1spt8mymy9g0dq9zb3ka4qj7h3",
		"id": "deluxe_membership"
	}]
}
Extension backend

There will be a new API at https://api.twitch.tv/helix/extensions/entitlements?extension_id=<extension_id>&user_id=<user_id>&channel_id=<channel_id> that will accept a JWT signed with the developer’s secret.
The response will look like:

{
	"entitlements": [{
	   // Consumable
		"domain": "twitch.ext.krim1spt8mymy9g0dq9zb3ka4qj7h3",
		"id": "game_token",
		"quantity": 32
	},
	{
		// Persistent
		"domain": "twitch.ext.krim1spt8mymy9g0dq9zb3ka4qj7h3",
		"id": "deluxe_membership"
	}]
}

Consuming an entitlement

Once a user has decided to consume one of their consumable entitlements, the Extension should start the consumption transaction by making the appropriate request, and then consume the benefit using the new Twitch APIs. Once an entitlement has been consumed, there is no way to undo that or give the user a new entitlement without spending Bits, so it’s important that the developer ensures the user gets the benefit before consuming their entitlement.

Front end

The Extension helper will have a new method added, consumeEntitlement(id, quantity) that will return the consumption transaction if the consumption initiated successfully, or an error if something went wrong during the consumption.

Extension backend

To initiate a consumption from the EBS, the EBS should make a POST request to the following URL.

https://api.twitch.tv/helix/extensions/consumptions/initiate

With the following JSON Body.

{
	"domain": "<extension_id>",
	"user": "<user_id>",
	"channel": "<channel_id>",
	"id": "<benefit_id>",
	"quantity": <number_to_consume>
}

The request should be signed with the developer secret.

Marking a consumption as fulfilled

Once you have actually given the user the benefit acquired with their consumable, you should mark that consumable as fulfilled, allowing the consumption to be completed. Failure to mark a consumption as fulfilled will result in the user being refunded their consumption.

Front end

The Extension Helper will have a new method added, markFulfilled(transactionId) that will return “success” once the transaction is marked as fulfilled.

Backend

Once a consumption notification has been sent, the developer will need to confirm that the user has received the benefits of their consumption by making a POST request to https://api.twitch.tv/helix/extensions/consumptions/confirm using your developer OAuth token with the following body.

{
	"transactionId": "7c916756-7aa9-4749-add4-ee754338763b"
}

This will ensure that the consumption transaction is marked as complete. If a transaction is not marked as complete after 5 minutes, the user will automatically be refunded their consumed goods.

Sample Use Cases

Panel Arcade

Our first example Extension is a simple panel Extension that allows users to play arcade-style games in the panel, in exchange for arcade tokens, which are acquired in exchange for Bits. Purchasing more tokens at a time infers a discount. Additionally, there is a “Deluxe Membership” that has a one time cost of 500 Bits, but allows the user one free play per hour. The user can also buy a “Deluxe Bundle” that includes the Deluxe Membership, as well as 10 tokens, for only 1000 Bits.

Defining benefits

First, we define the actual token as a benefit in the catalog system. We go to the Developer Rig, and navigate to the Manage Products tab. We then define our benefits to look like the following table:

Id Type Scope
game_token Consumable Channel
deluxe_member Persistent Extension

Defining token packages

Next we set up 5 new products with the following information:

Name Sku Amount In Development Broadcast Benefits
1 Token token_1 100 False False [{“game_token”, 1}]
5 Tokens token_5 400 False False [{“game_token”, 5}]
10 Tokens token_10 700 False False [{"game_token”, 10}]
Deluxe Membership deluxe_membership 500 False False [{“deluxe_member”}]
Deluxe Bundle deluxe_bundle 1000 False False [{“deluxe_member”},{“game_token”, 10}]

Note that we reuse the same benefit ID, with differing quantities for each pack. This allows users who purchase multiple packs to end up with the same entitlement, with a quantity of the summed packages. If a user buys two 10 Tokens products and a 5 Tokens product, they will end up with 25 game_token entitlements.

Modifying the front end

Now that we have our products defined, we can hook up the front end to take advantage of the new system. We hook up our available products the same way we did previously; we first wait for the onAuthorized callback, then make a getProducts request which returns our 5 products defined above. The new benefits array will come through, showing that we have defined benefits for our products. Additionally, we can now call getEntitlements(), which will return an array of entitlements, filtered to only show entitlements relevant to the current Extension. For new users, this array will be empty. Note that nothing prevents a user from purchasing both the VIP Membership and VIP Bundle, but they will only receive the “VIP Member” entitlement once. If this is undesirable behavior, you should remove both from availability for purchase in the front end once either has been purchased.

Consuming entitlements

Once a user has purchased their tokens, the getEntitlements() call will start returning their tokens. Once the user has decided to use their token, the extension will call consumeEntitlement(‘game_token’, 1) to consume 1 token. This call will return a JWT confirming that the transaction was initiated. The front end should then mark the consumption as fulfilled using markFulfilled(), and let the user play their game. Subsequent calls to getEntitlements() will show that the user has one fewer token.

Voting overlay

This example focuses on an application that is used to submit votes for a prediction poll on the current run of a battle royale game. Users will acquire votes with Bits, and then spend those votes to predict the outcome of a game, receiving points for successful predictions. They can choose to buy an Instant Vote that lets them vote on the current poll immediately, or they can choose to buy votes in bulk for multiple future polls.

Catalog management

Much like the first example, we have only one benefit to define - a consumable vote. This time we make two products, a single Instant Vote and a 10 Pack of Votes. When we’re done, our catalog looks like the following:

Benefits Table

Id Type Scope
vote Consumable Channel

Product Table

Name Sku Amount In Development Broadcast Benefits
Instant Vote instant_vote 25 False False [{‘vote’, 1}]
10 Pack of Votes votes_10 100 False False [{‘vote’, 10}]

Consumption strategy

The part that makes this scenario interesting is that from the user perspective, they have two different timing windows available. The Instant Vote consumes Bits, and fires immediately, while the 10 Pack of Votes lets them save votes for a later time; in reality both the Instant Vote and 10 Pack of Votes are defined almost identically, the difference is in the consumption strategy. For the Instant Vote, the Extension front end initiates the Bits transaction, and upon receiving a successful transaction verification, it fires off an event to the EBS to consume that vote immediately. To the user, this appears seamless.

Consumption flow

Once the user has decided how they want to vote, the Extension front-end code will send a message of your design to your EBS. Once the EBS receives that message, you should first ensure that the user has at least one vote entitlement with which to cast their vote. You would do so by making a GET call to the List entitlements endpoint above, which will return an array of entitlements. Once you’ve confirmed that the user does have enough votes to proceed, you should initiate the consumption transaction as outlined above. You can either do this synchronously and wait for the JWT to be returned to you, or asynchronously and wait for the notification if you’ve subscribed to the websub topic. Once the system has received the JWT containing the consumption transaction, the system should record the user’s vote in the backend system. Once that has succeeded, you should confirm the consumption as described above using the transaction ID in the consumption transaction.

Rollout considerations

Drawbacks

Only Bits transactions can create entitlements

Currently we don’t support the ability to create generic entitlements for an Extension. In the future we hope to have a proposal to allow developers to define and create entitlements for generic benefits that aren’t locked behind Bits, but need additional time to work through things like rate limits, and scaling for such a system.

Alternatives

Provide ability to check for individual entitlement

This is something the team considered for a while, but the complexity around what to return when an entitlement is persistent versus when the entitlement is consumable proved to be confusing enough that it wasn’t sufficiently less complex than simply getting all entitlements and iterating over them. Perhaps there will be a future state where fetching individual entitlements becomes simple enough to warrant this, but right now it doesn’t seem worthwhile.

4 Likes

I’m looking forward to this proposal being implemented, as I feel it would solve a lot of issues that are holding me back from diving into developing the extension I have been envisioning. I have one point of clarification and one suggestion:

Clarification: The Websub entitlement consumption topic, extension_consumption_request, is supposed to be sent in response to a request to consume an entitlement i.e. consumeEntitlement(id, quantity), correct? If that’s the case, shouldn’t it have a benefit_id attached to it rather than a product? As the example is currently displayed, it doesn’t make sense to me, unless I’m missing something.

Suggestion: It would greatly simplify my potential extensions if an additional user-defined string or very small JSON blob, possibly named userDefinedPayload or details or anything along those lines, could be included in the method to consumeEntitlement or the backend equivalent. This user-defined value would be sent unmodified along in the Websub consumption topic’s data object of the payload.

To explain the use case, in your second example of a voting overlay, when the user has decided how they want to vote, the Extension front-end code could directly call consumeEntitlement with a user-defined value of the item they are voting for. The backend, subscribed to the Websub topic, would get a notification of the vote, record it based on the user-defined value included in the data object, and confirm the consumption.

The advantages of this approach over the approach described above include reducing the number of calls to the Twitch API needed to be made and waited on by the backend service, as well as eliminating the need for the front-end code to communicate directly to the EBS. Then, if this is a simple extension, by having the valid items to be voted on distributed by the Configuration Service, viewer’s front-end code would never need to directly interact with the EBS at all, only with Twitch APIs. This would both simplify the code as well as reduce backend costs to the developer.

Hoping to see this RFC rolled out soon!

3 Likes