Add In-Skill Purchases to a Custom Skill

When you implement in-skill purchases in your skill you will need to do the following:

  • Call the in-skill product service to get the product inventory
  • Provide custom intents to enable purchases and handlers for those intents in your skill code
  • Send directives from your skill code to the purchase flow specifying a product for purchase
  • Resume your skill after the purchase flow completes
  • For consumable purchases, save the user's inventory of items in persistent storage and manage the inventory as the user uses the items

This topic provides the steps, code examples, best practices and links to more information.

Prerequisites

In order to complete the steps in this document you should have:

  • A skill with a custom interaction model, and associated Lambda function. See Steps to Build a Custom Skill for more information. Note that you can add in-skill products to skills hosted as web services, but the code in this topic demonstrates how to add in-skill products to a Lambda function.
  • A skill with at least one product added to your custom skill. For more information see Use the ASK CLI to Manage In-Skill Purchases

Get the in-skill products list

To offer in-skill purchases in your skill, you need to get the inventory of products available to the user in the current skill session. When you get the list, use it to:

  • Know the product ID for each product. You will need to map user requests for products to product IDs
  • Know what products can be offered for sale to this user (PURCHASABLE)
  • Know what this user has already purchased (products that are marked ENTITLED)
  • For consumable products, know how many units of the product this user has purchased as of today (activeEntitlementCount)

You get the list with a HTTP GET request to the InSkillProducts API of the in-skill product service. Add this call to your launch request handler, and store this list of products for the duration of the skill session. If the request is successful, the message result will contain an InSkillProducts object that contains a list of InSkillProduct objects.

Example

Copied to clipboard.

This sample code uses the Alexa Skills Kit SDK for Node.js (v2).

This LaunchRequest handler uses the MonetizationServiceClient provided in the SDK to call the InSkillProducts API and get the list of products. The welcome message then tells the user which products they have already purchased.

/*
    Function to demonstrate how to filter inSkillProduct list to get list of
    all entitled products to render Skill CX accordingly
*/
function getAllEntitledProducts(inSkillProductList) {
  const entitledProductList = inSkillProductList.filter(record => record.entitled === 'ENTITLED');
  return entitledProductList;
}

const LaunchRequestHandler = {
  canHandle(handlerInput) {
    return handlerInput.requestEnvelope.request.type === 'LaunchRequest';
  },
  handle(handlerInput) {

    const locale = handlerInput.requestEnvelope.request.locale;
    const ms = handlerInput.serviceClientFactory.getMonetizationServiceClient();

    return ms.getInSkillProducts(locale).then(
      function reportPurchasedProducts(result) {
        const entitledProducts = getAllEntitledProducts(result.inSkillProducts);
        if (entitledProducts && entitledProducts.length > 0) {
          // Customer owns one or more products

          return handlerInput.responseBuilder
            .speak(`Welcome to ${skillName}. You currently own ${getSpeakableListOfProducts(entitledProducts)}` +
              ' products. To hear a random fact, you could say, \'Tell me a fact\' or you can ask' +
              ' for a specific category you have purchased, for example, say \'Tell me a science fact\'. ' +
              ' To know what else you can buy, say, \'What can i buy?\'. So, what can I help you' +
              ' with?')
            .reprompt('I didn\'t catch that. What can I help you with?')
            .getResponse();
        }

        // Not entitled to anything yet.
        console.log('No entitledProducts');
        return handlerInput.responseBuilder
          .speak(`Welcome to ${skillName}. To hear a random fact you can say 'Tell me a fact',` +
            ' or to hear about the premium categories for purchase, say \'What can I buy\'. ' +
            ' For help, say , \'Help me\'... So, What can I help you with?')
          .reprompt('I didn\'t catch that. What can I help you with?')
          .getResponse();
      },
      function reportPurchasedProductsError(err) {
        console.log(`Error calling InSkillProducts API: ${err}`);

        return handlerInput.responseBuilder
          .speak('Something went wrong in loading your purchase history')
          .getResponse();
      },
    );
  },
}; // End LaunchRequestHandler   

Copied to clipboard.

This sample code uses the Alexa Skills Kit SDK for Node.js (v1).

This LaunchRequest handler constructs an HTTP GET request to call the InSkillProducts API and get the list of products. The getProductsAndEntitlements function takes a callback function parameter, makes the HTTP call asynchronously and then calls the callback function. The callback function processes the results once the HTTP call completes.


function onLaunch(launchRequest, session, callback) {
    console.log(`onLaunch requestId=${launchRequest.requestId}, sessionId=${session.sessionId}`);
    ...
    // call a function to get the available products
    getProductsAndEntitlements(this, functionToProcessProductListOnceItIsLoaded);
    ...    
}

function getProductsAndEntitlements(self, callback)   {
    // Invoke the entitlement API to load products only if not already cached
    if (!self.attributes.areProductsLoaded)    {
        self.attributes.inSkillProducts = [];
        var returnData = [];

        // Information required to invoke the API is available in the session
        const https = require('https');
        const apiEndpoint = "api.amazonalexa.com";
        const token  = "bearer " + self.event.context.System.apiAccessToken;
        const language    = self.event.request.locale;

        // The API path
        const apiPath     = "/v1/users/~current/skills/~current/inSkillProducts";

        const options = {
            host: apiEndpoint,
            path: apiPath,
            method: 'GET',
            headers: {
                "Content-Type"      : 'application/json',
                "Accept-Language"   : language,
                "Authorization"     : token
            }
        };

        // Call the API
            const req = https.get(options, (res) => {
            res.setEncoding("utf8");

            if(res.statusCode != 200)   {
                console.log("InSkillProducts returned status code " + res.statusCode);
                self.emit(":tell", "Something went wrong in loading the purchase history. Error code " + res.code );
            }

            res.on('data', (chunk) => {
                console.log("Chunk:" + chunk);
                    returnData += chunk;
            });

            res.on('end', () => {
                var inSkillProductInfo = JSON.parse(returnData);
                if(Array.isArray(inSkillProductInfo.inSkillProducts))  
                    self.attributes.InSkillProducts = inSkillProductInfo.inSkillProducts;
                else
                    self.attributes.InSkillProducts=[];

                console.log("Product list loaded:" + JSON.stringify(self.attributes.InSkillProducts));
                callback(self, self.attributes.InSkillProducts);
            });   
        });

        req.on('error', (e) => {
            console.log('Error calling InSkillProducts API: ' + e.message);
            self.emit(":tell", "Something went wrong in loading the product list. Error code " + e.code + ", message is " + e.message);
        });

    } // End if (!self.attributes.areProductsLoaded) {}
    else    {
        console.log("Product info already loaded.");
        callback(self, self.attributes.InSkillProducts);
        return;
    }
}

// Process the product list.
var functionToProcessProductListOnceItIsLoaded = function(self, inSkillProductList)  {
    if (!inSkillProductList)    {
        console.log("Something went wrong in loading product list.");
    }
    // Do something with the retrieved product list
    for (var idx = 0; idx < inSkillProductList.length; idx ++)   {
        console.log("inSkillProductList[" + idx + "] is:" + JSON.stringify(inSkillProductList[idx]));
    }
}

Add support for purchase requests

You will need to add support for a user to shop products and buy one by name in your skill. For example, you would want to support requests like the following:

  • What can I buy?
  • What can I shop for?
  • Tell me what I can buy
  • I want to buy product name
  • I want product name
  • Alexa, ask skill invocation name to give me product name

To support purchase requests, you:

  • Build a custom intent to support a purchase request
  • Add code to handle the custom intent, and start a purchase flow by sending a directive

Build the intent

The following JSON is an example of how you could model a custom intent to support a user request to buy a product by name. The sample intent lists variants to buy an item such as buy, purchase, want, would like, and a custom slot that contains a list of the in-skill products.

Example

{
  "intents": [
    {
      "name": "BuySkillItemIntent",
      "samples": [
        "buy",
        "shop",
        "buy {ProductName}",
        "purchase {ProductName}",
        "want {ProductName}",
        "would like {ProductName}"
      ],
      "slots": [
        {
          "name": "ProductName",
          "type": "LIST_OF_PRODUCT_NAMES"
        }
      ]
    }
  ],
  "types": [
    {
      "name": "LIST_OF_PRODUCT_NAMES",
      "values": [
        {
          "id": "reference_name",
          "name": {
            "value": "Product A",
            "synonyms": [
              "A product"
            ]
          }
        }
      ]
    }
  ]
}

Handle the intent

When you handle the buy intent, there are two scenarios your code must handle.

The user doesn't specify a product

The user may ask to shop the products you offer. In this case, you should direct the user to specify one product. In other words, your code detects a generic shop or buy request and responds to the user with a list of eligible products to choose from. When you give a list:

For example:

Alexa: I have 2 expansion packs available. Cave Quest. or Deep Sea. Which are you interested in?

The user specifies a product

The user may specify a product directly, or you may direct them to choose a product. Regardless, once the user specifies the product to buy, you then initiate the purchase flow by sending a Buy directive.

Your skill session ends when the purchase flow starts. Amazon handles the voice interaction model and all the mechanics of the purchase, as well as obtaining the product description and price from the product's schema. The message and price are automatically adjusted for Prime customers. When the purchase completes your skill will be re-launched with a new session, and a purchase result is supplied to your skill.

Example

The following example shows how you might add code to a Lambda function to send the Connections.SendRequest directive for a buy request.

Copied to clipboard.

This sample code uses the Alexa Skills Kit SDK for Node.js (v2).

return handlerInput.responseBuilder
        .addDirective({
            type: "Connections.SendRequest",
            name: "Buy",
            payload: {
                InSkillProduct: {
                    productId: "amzn1.adg.product.aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
                }
            },
            token: "correlationToken",
        })
        .getResponse();

Copied to clipboard.

This sample code uses the Alexa Skills Kit SDK for Node.js (v1).

this.handler.response = {
  'version': '1.0',
  'response': {
      'directives': [
          {
              'type': 'Connections.SendRequest',
              'name': 'Buy',          
              'payload': {
                         'InSkillProduct': {
                             'productId': 'amzn1.adg.product.aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'                       
                         }
               },
              'token': 'correlationToken'              
          }
      ],
      'shouldEndSession': true
  }
};

this.emit(":responseReady");

Copied to clipboard.

JSON syntax for a Connections.SendRequest directive for a purchase request. In this case, the name is Buy.

{
  "directives": [
    {
      "type": "Connections.SendRequest",
      "name": "Buy",
      "payload": {
        "InSkillProduct": {
          "productId": "amzn1.adg.product.aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
        }
      },
      "token": "correlationToken"
    }
  ]
}

Directive fields

Field Type Description Required
type String The type of directive. AlwaysSendRequest for a purchase flow Yes
name String Indicates the target for the SendRequest message. Always Buy for a purchase request. Yes
payload Object Contains details for the specified action. For a purchase request, this includes the InSkillProduct property with a product ID. Yes
token String A token to identify this message exchange and store skill information. The token is not used by Alexa, but is returned in the resulting Connections.Response. You provide this token in a format that makes sense for the skill. Yes
shouldEndSession Boolean Send true to indicate that the session should end. However, the session will always end after you send a SendRequest message. Yes

Offer purchase suggestions

Another way for users to buy your in-skill products is with a purchase suggestion, which means you proactively offer products related to how the user is currently interacting with your skill. You would check whether a user owns a product from the saved list and pass the product you want to suggest as well as a message relevant to that product to Amazon's purchase flow. To offer purchase suggestions, you:

  • Add code that starts the purchase flow with a directive.

For example:

After the user finishes content, the skill sends a directive that indicates an Upsell request. The skill session ends, and Alexa speaks the upsellMessage included in the directive.
Alexa: If you'd like more adventures to play, you can now get the Cave Quest expansion pack. Wanna learn more?
The skill sends a directive that indicates an Upsell request. This ends the current skill session.
User: Yes
For entitlements, Amazon provides the purchasePromptDescription and price before confirming the purchase.
For subscriptions, Amazon provides the price before confirming the purchase.

Send a directive to start the purchase suggestion

When you want to respond to a user intent with a purchase suggestion, you send a Connections.SendRequest directive to Alexa that:

  • Indicates a purchase suggestion by specifying upsell
  • Provides the identifier for the suggested product
  • Provides a message prompt for the purchase suggestion, the upsellMessage
  • Contains a token to store the current state of the skill or other relevant information. This is passed back to your skill in the response from the SendRequest call.

For the upsellMessage:

  • Never include price or offer details. Be careful not to reference the price, subscription term, or free trial length in any of the skill content. It is important that these values are obtained from your product description files. For example, Amazon may offer a discounted price if the user is a Prime member.
  • Determine whether a user is interested by making sure the message ends with an explicit confirmation question. Also do not ask whether they'd like to buy. Users make a buying decision after this step.
  • Help, don’t sell. Offer your product as a solution. Explain why your product is relevant at this moment, and what it does for the user.
  • Avoid suggesting products too often. You risk losing users when your suggestions begin to feel like interruptions. Start conservatively, than change the frequency over time to find the best approach.
  • Only make one suggestion. If a user isn't interested, continue where they left off. Don't suggest another product.

For more information about how to offer purchase suggestions, see How to Make a Purchase Suggestion.

Your skill session ends when the purchase flow starts. Amazon handles the voice interaction model and all the mechanics of the purchase, as well as obtaining the product description and price from the product's schema. The message and price are automatically adjusted for Prime customers. When the purchase completes your skill will be re-launched, and a purchase result is supplied to your skill.

Example

The following example shows how you might add code to a Lambda function to send the Connections.SendRequest directive for a purchase suggestion.

Copied to clipboard.

This sample code uses the Alexa Skills Kit SDK for Node.js (v2).

return handlerInput.responseBuilder
        .addDirective({
            type: "Connections.SendRequest",
            name: "Upsell",
            payload: {
                InSkillProduct: {
                    productId: "amzn1.adg.product.aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
                },
                upsellMessage: "This is my product...Do you want to know more?",
            },
            token: "correlationToken",
        })
        .getResponse();

Copied to clipboard.

This sample code uses the Alexa Skills Kit SDK for Node.js (v1).

this.handler.response = {
  'version': '1.0',
  'response': {
      'directives': [
          {
              'type': 'Connections.SendRequest',
              'name': 'Upsell',         
              'payload': {
                         'InSkillProduct': {
                            'amzn1.adg.product.aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'                          
                         },
                        'upsellMessage': 'This is my product...Do you want to know more?'
               },
              'token': 'correlationToken'   
          }
      ],
      'shouldEndSession': true
  }
};

this.emit(":responseReady");

Copied to clipboard.

JSON syntax for a Connections.SendRequest directive for an upsell. In this case, the name is Upsell and the payload includes the upsellMessage.

{
  "directives": [
    {
      "type": "Connections.SendRequest",
      "name": "Upsell",
      "payload": {
        "InSkillProduct": {
          "productId": "amzn1.adg.product.aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
        },
        "upsellMessage": "This is my product...Do you want to know more?"
      },
      "token": "correlationToken"
    }
  ]
}

Directive Fields

Field Type Description Required
type String The type of directive. AlwaysSendRequest for a purchase flow Yes
name String Indicates the target for the SendRequest message. Always Upsell for a purchase suggestion. Yes
payload Object Contains details for the specified action. For a purchase suggestion, this includes the InSkillProduct property with a product ID and the upsellMessage property with a custom message. Yes
upsellMessage String A product suggestion that fits the current user context. Should always end with an explicit confirmation question. Yes
token String A token to identify this message exchange and store skill information. The token is not used by Alexa, but is returned in the resulting Connections.Response. You provide this token in a format that makes sense for the skill. Yes
shouldEndSession Boolean Send true to indicate that the session should end. However, the session will always end after you send a SendRequest message. Yes

Resume your skill after the purchase flow

You need to correctly handle the result of a purchase flow and resume your skill so the user experience is smooth.

To do this:

  • You handle the Connections.Response request

The result of the purchase flow is a Connections.Response request that indicates whether the purchase was successful, and the token passed in the request, which you can use to help you resume where the user left off.

Use the guidelines in the sections that follow for details about how to handle each result type.

Example

The following JSON shows a Connections.Response request from a product offer.

{
    "type": "Connections.Response",
    "requestId": "string",
    "timestamp": "string",
    "name": "Upsell",
    "status": {
        "code": "string",
        "message": "string"  
    },
    "payload": {
        "purchaseResult":"ACCEPTED",    
        "productId":"string",   
        "message":"optional additional message"
    },
    "token": "string"
}

Connection.Response Request Fields

Name Type Description Required
type String The type of directive. Always Connections.Response for a purchase flow response Yes
requestId String The unique identifier for this request Yes
timeStamp String The time that this request was issued Yes
name String Indicates the target for the SendRequest message: Upsell for a purchase suggestion, Buy for a buy request, or Cancel for a cancellation or refund request. Yes
status Object Provides details of the HTTP status Yes
status.code String The HTTP response status such as 200, 400, 404  
status.message String The HTTP response message such as OK, Not Found, Not Authorized, etc.  
payload Object Contains details specific to the purchase transaction Yes
payload.purchaseResult string Indicates the result of the purchase transaction. Either ACCEPTED, DECLINED, ALREADY_PURCHASED, or ERROR. Yes
payload.productId String In-skill product that was sent in the buy or contextual message Yes
payload.message String Contains additional details about the transaction. No
token String The token passed by the skill when making the purchase request. Yes

You should check the status of the product returned in the purchaseResult using the InSkillProduct API, and use the following guidelines to handle the purchaseResult.

ACCEPTED result

What Alexa says:

  • One time purchase or consumable: "Great! You have successfully purchased in-skill product name"
  • Subscription: "Great. You now have in-skill product name"

What your skill should do:

Automatically start requested content or use the requested consumable, for example, "Let's play it now…" or "Here's your hint…" If the user commits to a purchase, make sure you complete their original request.

For a consumable product, your skill also needs to update the user's inventory of items in persistent storage associated with the userId provided in the request. See Manage the inventory for consumable purchases.

Example:

User completes their purchase
Alexa: You now have Cave Quest
Your skill launches
Alexa: Let's play it now

DECLINED result

What Alexa says:

  • One time purchase or consumable: "Okay"
  • Subscription: "No problem. You can ask me to sign up for in-skill product name anytime."

What your skill should do:

Pick up where the user left off and offer a way for the user to continue.

Example:

User declines the purchase
Alexa: Okay
Your skill launches and says:
Alexa: Let's choose an adventure from your collection. Which would you like to play?

ERROR result

What Alexa says:

The response depends on why the error occurred.

  • "Hmm, I had a problem with your payment method. To update, checkout the link I sent to your Alexa app."
  • "Sorry, I'm having trouble with that right now."
  • "I'm sorry. content title isn't available in your country."
  • "Sorry, I'm not able to make in-skill purchases on this device."

What your skill should do:

  • Pick up where the user left off, offer a way for them to continue.
  • Avoid mentioning that an error occurred. The user receives an explanation from the purchase flow before they return to your skill.
  • Do not ask the user if they'd like to try again.

Example:

User agrees to purchase, but needs to update payment information
Alexa: Hmm, I had a problem with your payment method. To update, checkout the link I sent to your Alexa app.
Your skill launches
Alexa: Let's choose an adventure from your collection. Which would you like to play?

ALREADY_PURCHASED result

This result can be returned for one-time purchases and subscriptions.

This result is never returned for consumables since consumable products can be purchased multiple times.

What Alexa says:

Good news, you already have in-skill product name

What your skill should do:

  • Automatically start the requested content.
  • Do not offer a different product

Example:

User asks to buy Crystal Catchers, but they already own it.
Alexa: Good news. You already own Crystal Catchers.
Your skill launches
Alexa: Let's choose an adventure from your collection. Which would you like to play?

Handle a refund or cancel request

You must also be able to handle a user request to refund a purchase and forward the request to the purchase flow.

A user might ask for a refund in one of the following ways:

  • Alexa, tell skill invocation name to return product name
  • Alexa, refund product name
  • I want to return product name
  • I want a refund for product name

Example:

User: Alexa, refund Cave Quest

Skill sends a Connections.SendRequest directive that indicates a Cancel request. This ends the current skill session.
Alexa: For a refund, check out the link I sent to your Alexa app.


Or a user might ask to cancel a subscription in one of the following ways:

  • Cancel my subscription for product name
  • Cancel the subscription for product name

Example:

User: Alexa, cancel my Treasure Finder Plus subscription.

Skill sends a Connections.SendRequest directive that indicates a Cancel request. This ends the current skill session.
Alexa: Okay. As a reminder, Treasure Finders Plus includes a collection of over 10 exclusive adventures, with a new one added each month. Are you sure you want to cancel your subscription?


To support cancellation or refund requests, you:

  • Build a custom intent to support a refund/cancellation request
  • Add code to handle the custom intent, and start a cancellation flow by sending a directive

Build the intent

The following JSON is an example of how you could model the custom intent to support a user request to refund a purchase. The sample intent lists variants to return or refund a product, and a custom slot that contains a list of the in-skill products.

Example

{
  "intents": [
    {
      "name": "RefundSkillItemIntent",
      "samples": [
        "return {ProductName}",
        "refund {ProductName}",
        "want a refund for {ProductName}",
        "would like to return {ProductName}"
      ],
      "slots": [
        {
          "name": "ProductName",
          "type": "LIST_OF_PRODUCT_NAMES"
        }
      ]
    }
  ],
  "types": [
    {
      "name": "LIST_OF_PRODUCT_NAMES",
      "values": [
        {
          "id": "reference_name",
          "name": {
            "value": "Product A",
            "synonyms": [
              "A product"
            ]
          }
        }
      ]
    }
  ]
}

Handle the intent

In your handler for the refund/cancel intent, you should send a message to the purchase flow that indicates a cancellation.

The following example shows how you might add code to send a Connections.SendRequest directive for a refund request.

Copied to clipboard.

This sample code uses the Alexa Skills Kit SDK for Node.js (v2).

return handlerInput.responseBuilder
        .addDirective({
            type: 'Connections.SendRequest',
            name: 'Cancel',
            payload: {
                InSkillProduct: {
                    productId: "amzn1.adg.product.aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
                }
            },
            token: "correlationToken"
        })
        .getResponse();

Copied to clipboard.

This sample code uses the Alexa Skills Kit SDK for Node.js (v1).

this.handler.response = {
  'version': '1.0',
  'response': {
      'directives': [
          {
              'type': 'Connections.SendRequest',
              'name': 'Cancel',            
              'payload': {
                         'InSkillProduct': {
                             'productId': 'amzn1.adg.product.aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'
                         }
               },
             'token': 'correlationToken'              
          }
      ],
      'shouldEndSession': true
  }
};

this.emit(":responseReady");

Copied to clipboard.

JSON syntax for a Connections.SendRequest directive for a cancel or refund request. In this case, the name is Cancel.

{
  "directives": [
    {
      "type": "Connections.SendRequest",
      "name": "Cancel",
      "payload": {
        "InSkillProduct": {
          "productId": "amzn1.adg.product.aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
        }
      },
      "token": "correlationToken"
    }
  ]
}

Directive fields

Field Type Description Required
type String The type of directive. AlwaysSendRequest for a purchase flow Yes
name String Indicates the target for the SendRequest message. Always Cancel for a refund or cancellation. Yes
payload Object Contains details for the specified action. For a cancel request, this includes the InSkillProduct property with a product ID. Yes
token String A token to identify this message exchange and store skill information. The token is not used by Alexa, but is returned in the resulting Connections.Response. You provide this token in a format that makes sense for the skill. Yes
shouldEndSession Boolean Send true to indicate that the session should end. However, the session will always end after you send a SendRequest message. Yes

Manage the inventory for consumable purchases

If you offer consumable products, you need to maintain the user's inventory of items as they purchase and use the items. Alexa keeps track of the total number times the user purchased a particular product, but this does not reflect the user's consumption of the purchased units. When your skill gets an ACCEPTED response for a consumable purchase, you need to:

  • Update the user's inventory in persistent storage to reflect the correct number of items. For instance, add five items if the user bought a "five hint pack".
  • Use the item if appropriate and decrement the inventory.
  • Save the inventory updates in persistent storage.

The ASK SDKs for Node.js, Java, and Python all include a PersistanceAdapter to save attributes. For details, see ASK SDK documentation about persistent attributes.

When storing the user's inventory in persistent storage, always map the inventory to the userId included in the request. The userId is always provided in the context object in the request (context.System.User.userId). The default persistence layer provided in the SDKs uses userId automatically.

For a sample skill that offers consumables and manages the inventory, see Name the Show.

Get the total purchases for a consumable product

To get the total number of times a user has purchased a particular consumable product, you can call the InSkillProducts API, then check the activeEntitlementCount property. The value for activeEntitlementCount reflects the total purchases the user has made. Each purchase of the product increments activeEntitlementCount by one. Each cancellation (such as due to a payment failure or refund request) decrements activeEntitlementCount by one.

Note that total purchases may not correspond to the number items the user can use in your skill if you offer multi-item packs. For example, your skill offers a "five hint pack" for sale. A user purchases this pack three times. With each purchase, you increment their inventory by five. In this case:

  • The activeEntitlementCount returned from the InSkillProducts API is 3
  • The inventory you maintain for the user contains 15

The user then uses 14 of the 15 hints. At this point:

  • The activeEntitlementCount returned from the InSkillProducts API is still 3
  • The inventory you maintain for the user contains 1

Validate the inventory on every request

It is possible for the user's number of entitled consumable purchases to change outside of a normal skill session. For example:

  • The user requested a refund for an accidental purchase. The next time the user opens your skill, activeEntitlementCount returns a smaller value than before. The user's inventory in your persistent storage reflects the user's pre-refund purchase.
  • During your own testing, you use the reset-isp-entitlement to clear your own purchases and start over. The next time you open your skill, activeEntitlementCount returns 0, but your own inventory in persistent storage reflects your earlier test purchases.

To account for cases like these, check and update the inventory on every incoming request. The ASK SDKs for Node.js, Java, and Python all include request interceptors, which are a useful way to execute code before your normal request handler. This can be a good way to implement this inventory check. For details, see ASK SDK documentation about request interceptors.

Maintain the user inventory if the user disables and re-enables the skill

If your skill offers consumable products, the user's userId remains the same even if the user disables and re-enables the skill. This ensures that the inventory of items that you maintain is not lost in this scenario.

You do not need to do anything to get this special userId behavior in this scenario. However, if you use the AlexaSkillEvent.SkillDisabled event to perform any cleanup after a user disables your skill, be sure to retain the user's consumable purchase inventory. You can check the userInformationPersistenceStatus property in the event object:

  • PERSISTED: If the user re-enables the skill, they will get the same userId, so do not clear their data.
  • NOT_PERSISTED: If the user re-enables the skill, they will get a new userId. Therefore, it is safe to clear data you have stored with the userId.

Additional Resources