Developer Console

Monetizing streaming and non-streaming apps for Amazon Fire TV and Fire tablets

Share:
Fire tablet Fire TV Make Money
Blog_Header_Post_Img

When you're building apps for Amazon devices, it's easy to assume the biggest differences lie between Amazon Fire tablet and Amazon Fire TV monetization. And yes—TV apps rely on remote navigation and cater to lean-back viewing, while tablet apps are designed for touch and on-the-go usage. But when it comes to monetization, the more meaningful distinction isn’t the device—it's the type of app you're offering.

The needs, expectations, and behaviors of users engaging with streaming video apps are fundamentally different from those using non-streaming apps (such as games, utilities, or educational tools). Streaming apps are typically built for continuous, passive engagement. They're designed to help users discover and consume long-form video content, often from the couch. In contrast, non-streaming apps are interactive and task-oriented—whether that means tapping through a game level, checking off a to-do list, or solving a puzzle.

This distinction directly impacts how you should approach monetization. In this post, we’ll review common monetization methods, go through some scenarios to align your monetization strategy with the type of app you’re developing, and then discuss how to use App Statistics Reports to optimize revenue and retention.

Common Monetization Methods

Before diving into specific implementation examples, it’s helpful to understand the primary Amazon Appstore monetization options available to you. While each app has unique needs, most monetization strategies fall into one of the following categories.

In-app purchasing (IAP)

In-app purchasing is one of the most flexible monetization options. You can offer:

  • Consumables: Purchases that are made, then consumed within the app, such as extra lives, extra moves, or in-game currency. These may be purchased multiple times.
  • Entitlements: One-time purchases to unlock features or content within an app or game.
  • Subscriptions: Premium content or features available for a regular fee.

Amazon IAP integration works across streaming and non-streaming apps.

In-app advertising

Ads can be integrated via supported SDKs from third-party advertising services. Formats include banner ads, rewarded videos, and interstitials or pre-roll video placements. This option is ideal for free apps with frequent user sessions.

Paid apps

Rather than monetizing via IAP, you can require users to pay upfront to install your app. This works best when your value proposition is immediately clear and ongoing content updates or upsells aren’t needed. However, if your app has unlockable or recurring content, IAP is likely a better fit.

Merch on Demand

With Merch on Demand, you can offer branded physical products—like shirts, mugs, or accessories—directly through your app. This is a great way to build brand engagement, especially if your app has a loyal or niche audience.

Product advertising

You can also link your app to Amazon’s retail catalog using the Product Advertising API. This is useful for apps tied to physical goods (like fitness, cooking, or tech), allowing you to surface relevant product listings and earn referral revenue.

Going this route requires you to have an Amazon Associates account and your app must meet the qualification requirements.

Putting Monetization into Practice: 4 Practical Scenarios

What does it look like to build monetization into your app? Here are four practical scenarios that show how you can implement both in-app purchase (IAP) strategies and non-IAP strategies across streaming and non-streaming apps.

Each example includes a brief use case, why the strategy fits, and a brief look at key implementation steps required to make it work. Whether you’re monetizing via paid apps, in-app purchasing, or advertising, these patterns can help you design your monetization plan—and start coding with confidence.
 

Scenario 1: Subscriptions via IAP in a streaming app

You're developing a fitness streaming app—offered on both Fire TV and Fire tablets—that offers a catalog of video workouts. Some content is available for free, but premium workout plans—such as multi-week programs or specialized training series—are only available to paying subscribers. To unlock access, users subscribe directly through the app using the Amazon Appstore IAP setup.

Why this works

Streaming apps often attract users who engage in long-session viewing and return regularly for new content. When the content is specialized or updated frequently—like guided workouts, wellness series, or instructional courses—subscriptions make it easy to gate premium value while encouraging retention. The limited free content can help whet users’ appetites for more, and encourage them to subscribe.

Key implementation steps

Using Amazon’s In-App Purchasing (IAP) SDK, you can offer subscription-based access that works across Fire TV and Fire Tablet devices. Subscription-based app monetization is managed by the Amazon Appstore, making billing and renewal handling easier on the developer side.

First, you will need to define your subscription SKU in the developer console. When managing your app, navigate to In-App Items and click Add Single IAP.

Monetizing streaming. In-app item screenshot

Specify a title and SKU for your subscription. Click Confirm.

Monetizing streaming. Create new subscription screenshot


Then, in your app code, implement the Appstore SDK IAP. This primarily involves using the PurchasingService and the PurchasingListener to communicate with Amazon about in-app purchases.

Second, add the ResponseReceiver to your AndroidManifest.xml file. For example:

Copied to clipboard
<application>
…
  <receiver
      android:name="com.amazon.device.iap.ResponseReceiver"
      android:permission="com.amazon.inapp.purchasing.Permission.NOTIFY"
      android:exported="true">
    <intent-filter>
      <action android:name="com.amazon.inapp.purchasing.NOTIFY" />
    </intent-filter>
  </receiver>
</application>


In the onResume method of your MainActivity.kt file, register your PurchasingListener implementation with the PurchasingService. Then, fetch user-specific data with getUserData and restore any active subscriptions with getPurchaseUpdates. This latter step is crucial for scenarios like app reinstalls or when a user accesses their subscription from a new device.

Copied to clipboard
override fun onCreate(savedInstanceState: Bundle?) {
  …
  myListener = MyPurchasingListener()
  …
}

…

override fun onResume() {
  super.onResume()
  PurchasingService.registerListener(this.applicationContext, myListener)
  PurchasingService.getUserData()
  PurchasingService.getPurchaseUpdates(false)
}


Your PurchasingListener interface will need to implement several handlers for asynchronous responses from the Amazon Appstore. Most importantly, you will need to implement onUserDataResponse, onProductDataResponse, onPurchaseResponse, and onPurchaseUpdateResponse.

Copied to clipboard
class MyPurchasingListener : PurchasingListener {

  override fun onUserDataResponse(response: UserDataResponse) {
    // Called on response to [PurchasingService.getUserData]
  }

  override fun onProductDataResponse(response: ProductDataResponse) {
    // Called on response to [PurchasingService.getProductData]
  }

  override fun onPurchaseResponse(response: PurchaseResponse) {
    // Called on response to [PurchasingService.purchase]
  }

  override fun onPurchaseUpdatesResponse(response: PurchaseUpdatesResponse)
  {
    // Called on response to [PurchasingService.getPurchaseUpdates]
  }


For example, handling the getProductData call would involve:

Copied to clipboard
override fun onProductDataResponse(response: ProductDataResponse) {
  val requestStatus: ProductDataResponse.RequestStatus = response.requestStatus
  when (requestStatus) {
    ProductDataResponse.RequestStatus.SUCCESSFUL -> {
      Log.i(TAG, "ProductDataResponse successful.")
      response.productData.forEach { sku, product ->
        Log.d(TAG, "Product: SKU: $sku, Type: ${product.productType}, Title: ${product.title}, Description: ${product.description}, Price: ${product.price}, SmallIconUrl: ${product.smallIconUrl}")
        // Use product information to update the app's UI
      }

      // Check for any SKUs that were requested but are not available.
      val unavailableSkus: Set<String> = response.unavailableSkus
      if (unavailableSkus.isNotEmpty()) {
        Log.w(TAG, "Unavailable SKUs: $unavailableSkus. These SKUs may be invalid or not published correctly in the Amazon Developer Console.")
      }
      
      // Enable purchase buttons for available products based on this data.
    }
    ProductDataResponse.RequestStatus.FAILED -> {
      Log.e(TAG, "ProductDataResponse failed. Could not retrieve product information.")
    }
    ProductDataResponse.RequestStatus.NOT_SUPPORTED -> {
      Log.w(TAG, "ProductDataResponse not supported. IAP may be unavailable.")
    }
    else -> {
      Log.w(TAG, "ProductDataResponse: Unhandled status: $requestStatus")
    }
  }
}


When a call to PurchasingService.purchase is made, use the onPurchaseResponse handler to retrieve information about the completed purchase, verify the purchase, and then grant the user access to the premium content.

Copied to clipboard
override fun onPurchaseResponse(response: PurchaseResponse) {
    val requestStatus: PurchaseResponse.RequestStatus = response.requestStatus
    when (requestStatus) {
      PurchaseResponse.RequestStatus.SUCCESSFUL -> {
        val receipt: Receipt? = response.receipt
        if (receipt != null) {
          Log.i(TAG, "PurchaseResponse successful. Receipt ID: ${receipt.receiptId}, SKU: ${receipt.sku}, Purchase Date: ${receipt.purchaseDate}, Cancel Date: ${receipt.cancelDate}")

          // IMPORTANT: Perform server-side receipt verification here.

          // Implement logic to grant premium content access to the user.
          Log.i(TAG, "Purchase for SKU ${receipt.sku} successful.")

          if (receipt.isCanceled) {
            // This state indicates the purchase was voided (e.g., by Amazon support). This is rare for a new successful purchase.
            Log.w(TAG, "Purchase marked as cancelled in onPurchaseResponse: ${receipt.receiptId}. Entitlement should be revoked after RVS confirms cancellation.")
            // Implement logic to revoke access to premium content
          }
        } else {
          Log.e(TAG, "PurchaseResponse successful, but receipt is null. This should not happen.")
      }
    }
    PurchaseResponse.RequestStatus.FAILED -> {
      // The purchase attempt failed (e.g., payment issue, user cancelled).
      Log.e(TAG, "PurchaseResponse failed. Do not grant entitlement.")
      
      // Inform the user that the purchase failed.
      //Optionally, provide a reason if available or guide them.
    }
    PurchaseResponse.RequestStatus.ALREADY_PURCHASED -> {
      // This status means the user already has this active subscription
      // This is not necessarily an error.
      // Ensure the user has access to the content.
      Log.i(TAG, "PurchaseResponse: SKU ${response.receipt?.sku} already purchased.")

      // Confirm the subscription is active.
      // This might involve checking local cache or re-validating with getPurchaseUpdates or receipt verification service

      // Refresh UI if necessary to reflect owned status.
    }
    PurchaseResponse.RequestStatus.INVALID_SKU -> {
      // The SKU provided in the purchase call was invalid or not recognized by Amazon.
      Log.e(TAG, "PurchaseResponse: Invalid SKU. SKU: ${response.receipt?.sku}. Check SKU definition in Amazon Developer Console and app code.")
    }
    PurchaseResponse.RequestStatus.NOT_SUPPORTED -> {
      // The purchase operation is not supported.
      Log.w(TAG, "PurchaseResponse not supported. IAP may be unavailable.")
    }
    else -> {
      Log.w(TAG, "PurchaseResponse: Unhandled status: $requestStatus")
    }
  }
}


Note that a SUCCESSFUL response status should be handled by verifying the purchase with the Receipt Verification Service (RVS). The receipt object (response.receipt) contains the purchase token (receipt.receiptId) and other details. This information must be sent to your backend server, which will then use it to validate the purchase with Amazon via the RVS. This prevents client-side tampering and confirms the purchase is legitimate.

The server-side logic for verifying a purchase involves:

1. App sends receipt.receiptId and response.userData.userId to secure backend server.

2. Server calls RVS, with the path based on the developer's shared secret, the customer's user ID, and the purchase receipt ID. For example:

Copied to clipboard
https://appstore-sdk.amazon.com/version/1.0/verifyReceiptId/developer/REPLACE-WITH-SHARED-SECRET/user/REPLACE-WITH-USER-ID/receiptID/REPLACE-WITH-RECEIPT_ID


3. Upon successful RVS response, you record the purchase and subscription status in your database, making sure to store receipt.startDate and receipt.endDate

4. You signal your app (via a secure API or a push notification) to unlock premium content.

Within your MainActivity definition, you will call PurchasingService.getProductData to retrieve information about your specific subscription IAP items based on their SKUs. For example:

Copied to clipboard
fun fetchSubscriptionProductData() {
  Log.d(TAG, "Fetch product data for subscriptions.")
  val subscriptionSkus: MutableSet<String> = HashSet()
  subscriptionSkus.add("subscription_mo_ptp_01")
  subscriptionSkus.add("subscription_yr_ptp_02")
  PurchasingService.getProductData(subscriptionSkus)
}


Initiating a purchase would look like this:

Copied to clipboard
fun initiatePurchase(sku: String) {
  if (sku.isBlank()) {
    Log.e(TAG, "SKU cannot be blank for initiating purchase.")
    return
  }
  Log.d(TAG, "Attempting to initiate purchase for SKU: $sku")
  PurchasingService.purchase(sku)
}

Implementing subscriptions for streaming app monetization is both technically straightforward and strategically sound. It aligns naturally with how users engage with video content—returning regularly for ongoing value—and gives developers a reliable, recurring revenue stream tied to meaningful user intent.
 

Scenario 2: Consumable and entitlement IAP in a non-streaming app

You're building a casual puzzle game for a Fire tablet. The core gameplay is free, but players can enhance their experience by purchasing bonus lives, hint packs, or cosmetic items. These purchases are also made through the SDK IAP. Non-subscription IAP items are either consumables (used once, like extra lives) or entitlements (unlocked permanently, like a new character skin).

Why this works

Non-streaming apps—especially games—tend to encourage short, frequent sessions where players are deeply engaged in active tasks. This behavior lends itself well to impulse purchases that offer instant rewards or progression boosts. With consumables and entitlements, you can monetize these micro-moments without putting core functionality behind a paywall.

Key implementation steps

Similar to setting up IAP subscription items, you would add your IAP items in the developer console, selecting Consumable or Entitlement as appropriate.

Monetizing streaming. In-app item screenshot


For each item, specify a title and a SKU. For example:

Monetizing streaming. create new consumable screenshot


Similarly to the subscription IAP item, implementing IAP for consumables and entitlements involves implementing and registering a PurchasingListener to handle responses from calls to the PurchasingService.

However, working with consumables is different from subscriptions in that PurchasingService.notifyFullfillment() should be called when the item has been "delivered" to the user. This notifies Amazon that the item has been provided, allowing the user to purchase that same consumable SKU again. The listener should also handle unfulfilled consumables found in onPurchaseUpdatesResponse by attempting to fulfill them, ensuring items are granted even if the app closed before fulfillment was previously completed.

For example, within the PurchasingListener implementation, you should have a method to handle fulfillment.

Copied to clipboard
private fun fulfillConsumable(receipt: Receipt) {
  val sku: String = receipt.sku
  val receiptId: String = receipt.receiptId

  Log.i(TAG, "Attempting to fulfill SKU: $sku with Receipt ID: $receiptId")
  // Conceptually, deliver the item to the user here.
  // For example, increment a counter for hints, lives, etc.
 
  // Notify Amazon that the item has been fulfilled.
  PurchasingService
    .notifyFulfillment(receiptId, FulfillmentResult.FULFILLED)
  Log.i(TAG, "Called notifyFulfillment for Receipt ID: $receiptId with FULFILLED")
}

That function, fulfillConsumable, is called by the onPurchaseResponse handler, once a receipt has been verified.

Copied to clipboard
override fun onPurchaseResponse(response: PurchaseResponse) {
  val skuFromReceipt: String = response.receipt?.sku ?: "N/A"

  when (response.requestStatus) {
    PurchaseResponse.RequestStatus.SUCCESSFUL -> {
      response.receipt?.let { currentReceipt ->
        val successMsg: String = "Purchase SUCCESSFUL for SKU: ${currentReceipt.sku}. ReceiptId: ${currentReceipt.receiptId}"
        if (currentReceipt.productType == ProductType.CONSUMABLE) {
          fulfillConsumable(currentReceipt)
        } else {
          // Handle other type of IAP
        }
      } ?: run {
        val errorMsg: String = "Purchase successful, but receipt is null. This should not happen."
        Log.e(TAG, errorMsg)
      }
    }
    PurchaseResponse.RequestStatus.FAILED -> {
      val failedMsg: String = "Purchase FAILED for SKU: $skuFromReceipt. Check logs."
      Log.e(TAG, failedMsg)
    }
    PurchaseResponse.RequestStatus.ALREADY_PURCHASED -> {
      val alreadyPurchasedMsg: String = "Purchase result ALREADY_PURCHASED for SKU: $skuFromReceipt."
      Log.w(TAG, alreadyPurchasedMsg)
      response.receipt?.let { currentReceipt ->
        if (currentReceipt.productType == ProductType.CONSUMABLE) {
          fulfillConsumable(currentReceipt)
        }
      }
    }
    PurchaseResponse.RequestStatus.INVALID_SKU -> {
      val invalidSkuMsg: String = "Purchase INVALID_SKU: $skuFromReceipt. Check SKU in developer console."
      Log.w(TAG, invalidSkuMsg)
    }
    PurchaseResponse.RequestStatus.NOT_SUPPORTED -> {
      val notSupportedMsg: String = "Purchase NOT_SUPPORTED. Check device capabilities or IAP setup."
      Log.w(TAG, notSupportedMsg)
    }
    PurchaseResponse.RequestStatus.PENDING -> {
      val pendingMsg: String = "Purchase PENDING for SKU: $skuFromReceipt. Waiting for resolution (e.g. parental approval)."
      Log.i(TAG, pendingMsg)
      // Typically, no action is taken here by the app. The Amazon
      // Appstore will handle the pending state and a new
      // onPurchaseResponse or onPurchaseUpdatesResponse will be 
      // sent when it resolves.
    }
    null -> {
      val nullMsg: String = "PurchaseResponse status is NULL (or unhandled). This should not happen."
      Log.e(TAG, nullMsg)
    }
  }
}

Adding consumables or entitlements through IAP is a flexible and user-friendly way to do non-streaming app monetization. It aligns with user behavior, keeps the core experience accessible, and gives users the option to pay for meaningful in-game (or in-app) advantages or customizations.
 

Scenario 3: In-App ads in a non-streaming app using a supported SDK

You're developing a productivity app—something like a focused task manager or guided journaling app. The app is free to use, but you want to monetize by showing rewarded video ads. For example, users could unlock premium templates or remove a daily usage cap by watching an ad. To do this, you would integrate a supported third-party ad SDK (such as Chartboost or AdColony).

Why this works

Non-streaming apps often involve short, frequent sessions, making them ideal for rewarded or interstitial ads that don't interrupt long-form content. With rewarded video, users get clear value in exchange for their attention, and developers get monetization without adding a paywall. This model works especially well in utility and casual app categories where repeat engagement is common.

Key implementation highlights

Begin by selecting an Amazon-supported ad SDK. For the following example, we will use the Chartboost Android SDK.

In your application, implement a new Activity (for example, RewardedAdsActivity.kt) that imports the Chartboost SDK.

Copied to clipboard
import com.chartboost.sdk.Chartboost
import com.chartboost.sdk.ads.Rewarded
import com.chartboost.sdk.LoggingLevel
import com.chartboost.sdk.events.*
import com.chartboost.sdk.callbacks.RewardedCallback
import com.chartboost.sdk.privacy.model.DataUseConsent

In the onCreate function for the RewardedAdsActivity, initialize the SDK.

Copied to clipboard
// Replace with your actual Chartboost App ID and App Signature
private val chartboostAppID: String = "CHARTBOOST_APP_ID"
private val chartboostAppSignature: String = "CHARTBOOST_APP_SIGNATURE"

// Define a default location for rewarded ads
const val REWARDED_AD_LOCATION = "default-rewarded_video"

…

override fun onCreate(savedInstanceState: Bundle?) {
  …

  // It's important to set data use consent before initializing the SDK.
  // In a real app, this would be based on user consent.
  // For this example, we'll use placeholders.
  Chartboost.addDataUseConsent(this, DataUseConsent.GDPR("1"))
  Chartboost.addDataUseConsent(this, DataUseConsent.CCPA("1---"))

  // Enable verbose logging. Call this before startWithAppId.
  Chartboost.setLoggingLevel(LoggingLevel.ALL)

  Chartboost.startWithAppId(
    applicationContext, chartboostAppID, chartboostAppSignature
  ) { startError ->
        if (startError == null) {
          val message = "Chartboost SDK Initialized Successfully."
          Log.d("ChartboostInit", message)
          // Pre-cache ad after successful initialization
          loadRewardedAd()
        } else {
          val errorMessage = "Chartboost SDK Initialization Failed: ${startError.code.name}"
          Log.e("ChartboostInit", errorMessage)
        }
   }
}
…


Rewarded ads must be cached, so you need to implement loading and caching of the ad in order to show it. The following functions, defined within RewardedAdsActivity, demonstrate how you might do this.

Copied to clipboard
private fun loadRewardedAd() {
  if (rewardedAd == null || !rewardedAd!!.isCached()) {
    createAndCacheRewardedAd()
  } else {
    Log.d("ChartboostRewarded", "Rewarded ad for $REWARDED_AD_LOCATION is already cached.")
    showAdButton.isEnabled = true
  }
}

private fun createAndCacheRewardedAd() {
  rewardedAd = Rewarded(REWARDED_AD_LOCATION, object : RewardedCallback {
  
    override fun onAdLoaded(event: CacheEvent, error: CacheError?) {
      if (error != null) {
        val errorMessage = "Rewarded Ad failed to load for location ${event.ad.location}: ${error.code.name}"
        Log.e("ChartboostRewarded", errorMessage)
        showAdButton.isEnabled = false
      } else {
        Log.d("ChartboostRewarded", "Rewarded Ad loaded successfully for location ${event.ad.location}.")
        showAdButton.isEnabled = true
      }
    }

    override fun onAdRequestedToShow(event: ShowEvent) {
      Log.d("ChartboostRewarded", "Rewarded Ad requested to show for location ${event.ad.location}.")
    }

    override fun onAdShown(event: ShowEvent, error: ShowError?) {
      if (error != null) {
        Log.e("ChartboostRewarded", "Rewarded Ad failed to show for location ${event.ad.location}: ${error.code.name}")
      } else {
        Log.d("ChartboostRewarded", "Rewarded Ad shown successfully for location ${event.ad.location}.")
      }
    }

    override fun onAdClicked(event: ClickEvent, error: ClickError?) {
      val message = if (error == null) {
        "Rewarded Ad clicked for location ${event.ad.location}."
      } else {
        "Rewarded Ad click error for location ${event.ad.location}: ${error.code.name}"
      }
      Log.d("ChartboostRewarded", message)
    }

    override fun onAdDismiss(event: DismissEvent) {
      Log.d("ChartboostRewarded", "Rewarded Ad dismissed for location ${event.ad.location}.")
      showAdButton.isEnabled = false
      
      // It's good practice to cache the next ad after one is dismissed.
      loadRewardedAd()
    }

    override fun onRewardEarned(event: RewardEvent) {
      Log.d("ChartboostRewarded", "User earned reward: ${event.reward} for watching ad at location ${event.ad.location}.")

      // Grant the reward (for example: 10 coins) to the user here:
      // val currentCoins = sharedPreferences.getInt("user_coins", 0)
      // sharedPreferences.edit().putInt("user_coins", currentCoins + event.reward).apply()
      // updateUiWithNewCoinBalance(currentCoins + event.reward)
      // Log.d("ChartboostRewarded", "User awarded ${event.reward} coins. New balance: ${currentCoins + event.reward}")
     

    }

    override fun onImpressionRecorded(event: ImpressionEvent) {
      Log.d("ChartboostRewarded", "Rewarded Ad impression recorded for location ${event.ad.location}.")
    }
  }, null)

  rewardedAd?.cache()
}

private fun showRewardedAd() {
  if (rewardedAd != null && rewardedAd!!.isCached()) {
    rewardedAd?.show()
  } else {
    Log.w("ChartboostRewarded", "Attempted to show ad at $REWARDED_AD_LOCATION but it's not cached.")
    showAdButton.isEnabled = false
    
    // Attempt to load an ad if not available
    loadRewardedAd()
  }
}

The above implementation centers around creating a Rewarded ad object. This is a full-screen ad that provides a reward to the user. When creating the Rewarded object, the RewardedCallback to handle various ad lifecycle events:

Ad callbacks

  • onAdLoaded: Triggered when an ad is successfully cached and ready to be shown. Update the UI here (for example, by enabling the "Show Ad" button), but also handle a failed load.
  • onAdShown: Triggered when the ad is displayed to the user.
  • onAdClicked: Triggered if the user clicks on the ad.
  • onImpressionRecorded: Triggered after an ad has recorded an impression.

Dismissible ad callbacks

  • onAdDismiss: Triggered when the ad is closed by the user. It's best practice, at this point, to load the next rewarded ad to ensure availability.

Rewarded callbacks

  • onRewardEarned: Triggered when the user has successfully watched the video to completion. This is a crucial callback. The RewardEvent provides the amount of reward. Grant the actual reward to the user here (for example, updating currency or unlocking an item).

Rewarded ads are a user-friendly monetization path for non-streaming apps that offer repeated, task-based interactions. By integrating a supported SDK and clearly connecting ads to user benefits, you can monetize without compromising the experience.


Scenario 4: In-app ads in a streaming app using a supported ad SDK

You’re building a niche video streaming app that offers curated content—like indie films and music videos. Your goal is to keep the content free while generating revenue through in-app advertising. The typical way to achieve this is by implementing a supported ad SDK that can deliver pre-roll and interstitial ads in streaming apps.

For enterprise-scale apps with high volumes of content and traffic, Amazon offers Amazon Publisher Services (APS)—a programmatic advertising solution that enables real-time bidding from Amazon and third-party advertisers. However, APS is only available through a selective application process. For the majority of streaming apps, integrating a compatible video ad SDK remains the most practical and effective monetization option.

Why this works

Streaming apps are uniquely suited for ad-based monetization, especially when users expect free content and when session lengths are long enough to accommodate multiple ad placements. Whether you insert ads during natural pauses between videos or as pre-roll before playback, streaming environments offer ample opportunities for high-value impressions—particularly on Fire TV, where video ads can be displayed at full-screen.

Key implementation highlights

Just as in the previous scenario, you would begin by selecting an Amazon-supported ad SDK. However, instead of a rewarded ad, you would show an interstitial ad.

Within a streaming app, you would place interstitial ads at natural breakpoints. A common approach is to place an interstitial ad at pre-roll, upon a user request to play video content. After the entire interstitial ad is watched, your app would show the requested video content to the user.

The implementation of interstitial ads with Chartboost is very similar to that of rewarded ads. As in the previous scenario, you would begin by:

  • Importing the Chartboost SDK.
  • Calling addDataUseConsent to specify whether consent for this user exists, does not exist, or is unknown.
  • Setting appropriate logging levels for the SDK.
  • Calling startWithAppId with your app ID and app signature to initialize the SDK.

Then you would create an Interstitial object that implements methods within the InterstitialCallback:

Copied to clipboard
interstitialAd = Interstitial("location", object : InterstitialCallback {

  override fun onAdDismiss(dismissEvent: DismissEvent) {
    // Called when an ad is dismissed
    // Return user to video gallery without playing the requested video
  }

  override fun onAdLoaded(cacheEvent: CacheEvent, cacheError: CacheError) {
    // Ad has been loaded from Chartboost servers and cached
    // If no CacheError, then ad is ready to be shown
  }

  override fun onAdRequestedToShow(showEvent: ShowEvent) {
    // Called right before an ad is presented
  }

  override fun onAdShown(showEvent: ShowEvent, showError: ShowError?) {
    // Called after ad has been presented or impression has been logged
    // Proceed to playing the video requested by the user
  }

  override fun onAdClicked(clickEvent: ClickEvent, clickError: ClickError?)
  {
    // Called after an ad has been clicked
    // Use this if you want to capture (log, notify) when this occurs
  }

  override fun onImpressionRecorded(impressionEvent: ImpressionEvent) {
    // Called after an ad has recorded an impression
    // Use this if you want to be capture (log, notify) when this occurs
  }

}, null)

To cache the interstitial, call:

Copied to clipboard
interstitialAd.cache()

Then, when you're ready to show the ad (after the user requests a video to watch, but before the video begins streaming), call:

Copied to clipboard
if (interstitialAd.isCached()) {
  interstitialAd.show()
}

Ad monetization in streaming apps can be highly effective—especially when ads are relevant, well-placed, and non-disruptive. Whether you're using a supported ad SDK or APS, thoughtful integration ensures a balance between monetization and user experience.

Monitoring and Optimizing Monetization

Once you’ve implemented your monetization strategy, the next step is understanding how it’s performing—and where to improve. Amazon’s Monetization Reports provide a helpful starting point, with dashboards that cover revenue trends, active subscriptions, retention, and churn. If you haven’t explored them yet, we recommend reading this post, which introduces Monetization Reports and provides a full overview.

Monetization Reports are just one type of report among the broader set of App Statistics Reports. For day-to-day insights, these reports give you detailed data on how users are engaging with your app. These reports are broken into three key areas:
 

Acquisition (Fire TV only)

These help you understand how users discover and install your app on Fire TV. The Acquisition Report shows top-level traffic sources, including category pages, search results, and Amazon-curated placements like “Recommended for You.”

You’ll see which keywords are driving the most installs, how frequently your app appears in search, and what percentage of impressions result in clicks or installs. This data helps you optimize your store listing, titles, and metadata to improve visibility and conversion.

Reporting dashboard screenshot


Engagement

Track how users interact with your app after installation. Engagement Reports show metrics like daily and monthly active users, total session counts, average session length, and stickiness (DAU/MAU ratio). These insights help you understand usage patterns and identify high-retention segments. You can also break down engagement by app version, enabling you to gauge the impact of feature changes or bug fixes on user activity over time.

Engagement reporting dashboard screenshot


Monetization

To understand monetization trends for your subscription IAP items, look to the Monetization Reports. These include the Subscription Overview, Retention, and Cancellation reports, which help you track subscriber behavior—such as how many users start, maintain, or cancel subscriptions—along with insights into average duration and cancellation reasons.

Monetization reporting dashboard screenshot


For one-time purchases (like consumables or entitlements) and ad-based models, you’ll want to rely on other App Statistics Reports for aggregate IAP event data. For advertising revenue specifically, use your ad SDK provider’s dashboard to track key metrics like impressions, fill rate, and eCPM. Together, these tools give you the visibility to refine monetization strategies and respond to user behavior with data-driven updates.

Together, the App Statistics Reports provide powerful app monetization reports and analysis. Use them to refine your monetization timing, test feature placements, and make more informed decisions about what to optimize next.

Closing Thoughts

Whether you’re building a streaming app or a task-based utility, your monetization strategy should match how users actually engage with your content. Subscriptions, consumables, rewarded ads, and programmatic video all have their place—it’s about choosing the right tool for the right context.

These SDKs, APIs, and developer tools give you the flexibility to monetize across all app types and devices. With the right implementation and a close eye on performance data, you can build an app that delivers value to users while generating meaningful revenue for your business.

Related articles

Sign up for our newsletter

Stay up to date with the latest Amazon Developer news, industry trends and blog posts.