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:
- Allow Extension developers to more accurately model their product catalog.
- Remove errors in message passing, giving a greater confidence for all parties involved that users are properly getting the benefits of their digital goods.
- 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:
- 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.
- Fulfillment - The EBS will then need to give the user the benefit associated with the entitlement.
- 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.