Migration Guide from Outbrain SDK to Teads Unified SDK
This guide helps developers currently using the Outbrain SDK transition to the new Teads Unified SDK. Following the merger of Teads and Outbrain, we've created a unified SDK that maintains all Outbrain functionality while adding powerful new capabilities.
Important Notice
⚠️ The standalone Outbrain SDK is being deprecated
✅ All Outbrain features are preserved in the Teads Unified SDK
New features and improvements are available
Migration is required but straightforward
Overview of Changes
What's Changing
| Aspect | Outbrain SDK | Teads Unified SDK | 
|---|---|---|
| SDK Name | OutbrainSDK | TeadsSDK | 
| Import Statement | import OutbrainSDK | import TeadsSDK | 
| Initialization | Outbrain.initializeOutbrain() | Teads.configure(applicationContext: Context, appKey: String) | 
| Widget Class | SFWebViewWidget/SFWebViewWidgetPolling | TeadsAdPlacementFeed | 
| Recommendations | OBRequest | TeadsAdPlacementRecommendations | 
| Configuration | Builder pattern | Configuration objects | 
What's Preserved
✅ All widget functionality
✅ Content recommendation algorithms
✅ Viewability tracking
✅ User personalization
✅ Dark mode support
✅ Event tracking
What's New
🎁 Access to Teads video ad formats
🎁 Unified event system
🎁 Improved performance
🎁 Enhanced privacy controls
Step-by-Step Migration
Step 1: Update Dependencies
project/build.gradle
repositories {
    maven { url  "https://teads.jfrog.io/artifactory/SDKAndroid-maven-prod" }
    maven {
        // Mandatory for Huawei device compatibility
        url  "https://developer.huawei.com/repo/"
    }
}
app/build.gradle
dependencies {
      // Remove legacy dependency
      // implementation "com.outbrain.mobile:obsdk:$version
       
      // Add new Teads SDK
      implementation("tv.teads.sdk.android:sdk::6.x.x@aar") {
        transitive = true
      }
}
Manual Installation
- Download the latest TeadsSDK .aar file
- Copy it to your app's libsfolder
- Add to your build.gradle:
dependencies {
    implementation files('libs/sdk-6.x.x.aar') {
        transitive = true
    }
}
Step 2: Update Imports
Replace all Outbrain imports with Teads:
// Before
import com.outbrain.*
// After
import tv.teads.sdk.*
Step 3: Update SDK Initialization
Before (Outbrain SDK)
// App initialization
Outbrain.register(applicationContext, "YOUR_PARTNER_KEY")
// Configuration for testing builds
if (BuildConfig.DEBUG) {
    Outbrain.setTestMode(true)
    Outbrain.testLocation("us") // Optional: Test specific geolocations
}
After (Teads Unified SDK)
//  App initialization
TeadsSDK.configure(applicationContext, "YOUR_PARTNER_KEY")
// Configuration for testing builds
if (BuildConfig.DEBUG) {
    TeadsSDK.testMode = true
    TeadsSDK.testLocation = "us" // Optional: Test specific geolocations
}
Step 4: Migrate Widget Implementation
The widget functionality is now provided through TeadsAdPlacementFeed.
Before (Outbrain SDK)
import android.content.res.Configuration
import android.os.Bundle
import android.os.Handler
import android.util.Log
import android.view.ViewGroup
import android.widget.ScrollView
import androidx.annotation.NonNull
import androidx.appcompat.app.AppCompatActivity
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.outbrain.OBSDK.SFWebView.SFWebViewClickListener
import com.outbrain.OBSDK.SFWebView.SFWebViewHeightDelegate
import com.outbrain.OBSDK.SFWebView.SFWebViewWidget
import com.outbrain.catalog.databinding.ActivitySfwidgetScrollviewBinding
class SFWebViewScrollViewActivity : AppCompatActivity(), SFWebViewClickListener {
    private val LOG_TAG = "SFWebViewScrollActivity"
    private lateinit var binding: ActivitySfwidgetScrollviewBinding
    private lateinit var sfWidget: SFWebViewWidget
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivitySfwidgetScrollviewBinding.inflate(layoutInflater)
        setContentView(binding.root)
        // Initialize widget
        sfWidget = SFWebViewWidget(
            binding.adScrollView,
            "http://mobile-demo.outbrain.com",
            "MB_2", // Widget ID
            0, // Widget Index
            "NANOWDGT01", // Installation Key
            this, // SFWebViewClickListener
            false // Dark Mode
        )
        // Add widget to layout
        binding.adContainerView.addView(sfWidget)
        // Optional: Handle clicks on organic urls
        sfWidget.setSfWebViewClickListener(this)
        
        // Toggle dark mode at your convenience
        sfWidget.toggleDarkMode(true)
    }
    override fun onConfigurationChanged(newConfig: Configuration) {
        super.onConfigurationChanged(newConfig)
        sfWidget.onActivityConfigurationChanged()
    }
    override fun onOrganicClick(url: String) {
        // Programmatically open browser with the url
    }
}
After (Teads Unified SDK)
package teads.tv.integrationtests
import android.content.res.Configuration
import android.os.Bundle
import android.util.Log
import androidx.core.net.toUri
import teads.tv.integrationtests.databinding.ActivitySfwidgetScrollviewBinding
import tv.teads.sdk.combinedsdk.TeadsAdPlacementEventName
import tv.teads.sdk.combinedsdk.adplacement.TeadsAdPlacementFeed
import tv.teads.sdk.combinedsdk.adplacement.config.TeadsAdPlacementFeedConfig
import tv.teads.sdk.combinedsdk.adplacement.interfaces.TeadsAdPlacementEventsDelegate
import tv.teads.sdk.combinedsdk.adplacement.interfaces.core.TeadsAdPlacement
class SFWebViewScrollViewActivity : AppCompatActivity(), TeadsAdPlacementEventsDelegate {
    private lateinit var teadsAdPlacementFeed: TeadsAdPlacementFeed
    private lateinit var binding: ActivitySfwidgetScrollviewBinding
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityFeedScrollableBinding.inflate(layoutInflater)
        setContentView(binding.root)
        val feedConfig = TeadsAdPlacementFeedConfig(
            articleUrl = "https://mobile-demo.outbrain.com/".toUri(),
            widgetId = "MB_1",
            installationKey = "NANOWDGT01",
            widgetIndex = 0
        )
        
        teadsAdPlacementFeed = TeadsAdPlacementFeed(
            context = this,
            config = feedConfig,
            delegate = this // TeadsAdPlacementEventsDelegate
        )
        // Load feed
        val feedAdView = placement.loadAd()
        
        // Add feed to layout
        binding.containerAdView.addView(feedAdView)
        // Toggle dark mode at your convenience
        teadsAdPlacementFeed.toggleDarkMode(true)
    }
    override fun onConfigurationChanged(newConfig: Configuration) {
        super.onConfigurationChanged(newConfig)
        teadsAdPlacementFeed?.onActivityConfigurationChanged()
    }
    override fun onPlacementEvent(
        placement: TeadsAdPlacement<*, *>,
        event: TeadsAdPlacementEventName,
        data: Map<String, Any>?
    ) {
        // Listen the ad lifecycle events
        if (placement is TeadsAdPlacementFeed && event == TeadsAdPlacementEventName.CLICKED_ORGANIC) {
            val url = data?.get("url") as? String
            url?.let {
                // Programmatically open browser with the url 
            }
        }
    }
}
Step 5: Migrate Recommendations API
Before (Outbrain SDK)
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.outbrain.OBSDK.Entities.OBRecommendationsResponse
import com.outbrain.OBSDK.FetchRecommendations.OBRequest
import com.outbrain.OBSDK.FetchRecommendations.RecommendationsListener
import com.outbrain.OBSDK.Outbrain
import com.outbrain.catalog.databinding.ActivityRecommendationsScrollviewBinding
class RecommendationsScrollViewActivity : AppCompatActivity(), RecommendationsListener {
    private val LOG_TAG = "RecommendationsScrollViewActivity"
    private lateinit var binding: ActivityRecommendationsScrollviewBinding
    private lateinit var recommendationsList: ArrayList<OBRecommendation>
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityClassicWithImageBinding.inflate(layoutInflater)
        setContentView(binding.root)
        // Request recommendations
        loadRecommendations()
    }
    private fun loadRecommendations() {
        val request = OBRequest()
        request.setWidgetId("SDK_1")
        request.setUrl("http://mobile-demo.outbrain.com/2013/12/15/test-page-2")
        request.setWidgetIndex(0)
        Outbrain.fetchRecommendations(request, this)
    }
    override fun onOutbrainRecommendationsSuccess(recommendationsResponse: OBRecommendationsResponse) {
        recommendationsResponse.all.forEach { recommendation ->
            // Iterate recommendationsResponse list and inflate your views
            // Configure viewability tracking
            Outbrain.configureViewabilityPerListingFor(recommendationContainerView, recommendation)
        }
    }
    override fun onOutbrainRecommendationsFailure(ex: Exception) {
        // Failure callback
        ex.printStackTrace()
    }
}
After (Teads Unified SDK)
import android.os.Bundle
import android.util.Log
import androidx.core.net.toUri
import com.outbrain.OBSDK.Entities.OBRecommendationsResponse
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import tv.teads.sdk.combinedsdk.TeadsAdPlacementEventName
import tv.teads.sdk.combinedsdk.adplacement.TeadsAdPlacementRecommendations
import tv.teads.sdk.combinedsdk.adplacement.config.TeadsAdPlacementRecommendationsConfig
import tv.teads.sdk.combinedsdk.adplacement.interfaces.TeadsAdPlacementEventsDelegate
import tv.teads.sdk.combinedsdk.adplacement.interfaces.core.TeadsAdPlacement
class RecommendationsScrollViewActivity : BaseActivity(), TeadsAdPlacementEventsDelegate {
    private lateinit var teadsAdPlacementRecommendations: TeadsAdPlacementRecommendations
    private lateinit var binding: RecommendationsScrollViewActivityBinding
    // Custom coroutine scope
    private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = RecommendationsScrollViewActivityBinding.inflate(layoutInflater)
        setContentView(binding.root)
        // Request recommendations
        loadRecommendations()
    }
    private fun loadRecommendations() {
        teadsAdPlacementRecommendations = TeadsAdPlacementRecommendations(
            config = TeadsAdPlacementRecommendationsConfig(
                articleUrl = "http://mobile-demo.outbrain.com/2013/12/15/test-page-2".toUri(),
                widgetId = "SDK_1",
                widgetIndex = 0
            ),
            delegate = this
        )
        coroutineScope.launch {
            try {
                val recommendations = teadsAdPlacementRecommendations.loadAdSuspend()
                displayRecommendations(recommendations)
            } catch (exception: Exception) {
                Log.d(
                    "TeadsAdPlacementRecommendations",
                    "Failed to load, reason: ${exception.message}"
                )
            }
        }
    }
    private fun displayRecommendations(recommendations: OBRecommendationsResponse) {
        recommendations.all.forEach { recommendation ->
            // Iterate recommendationsResponse list and inflate your views
            // Configure viewability tracking
            TeadsAdPlacementRecommendations.configureViewabilityPerListingFor(recommendationContainerView, recommendation)
        }
    }
    override fun onDestroy() {
        super.onDestroy()
        coroutineScope.cancel()
    }
    override fun onPlacementEvent(
        placement: TeadsAdPlacement<*, *>,
        event: TeadsAdPlacementEventName,
        data: Map<String, Any>?
    ) {
        // Failure callback
        if (placement is TeadsAdPlacementRecommendations
            && event == TeadsAdPlacementEventName.FAILED
        ) {
            Log.d(
                "TeadsAdPlacementRecommendations",
                "Failed to load, reason: ${data?.get("error")}"
            )
        }
    }
}
Java Compatibility: If your implementation requires Java or you prefer not to use coroutines, you can use the observable-based loading approach with .subscribe() instead of the suspend function.
private fun loadRecommendations() {
    teadsAdPlacementRecommendations = TeadsAdPlacementRecommendations(
        config = TeadsAdPlacementRecommendationsConfig(
            articleUrl = "http://mobile-demo.outbrain.com/2013/12/15/test-page-2".toUri(),
            widgetId = "SDK_1",
            widgetIndex = 0
        ),
        delegate = this
    )
    teadsAdPlacementRecommendations
        .loadAd()
        .subscribe { recommendations ->
            displayRecommendations(recommendations)
        }
}
Configurations migration map
SDK top level configurations
| Legacy Outbrain.java Method | New TeadsSDK.kt Method | Notes | 
|---|---|---|
| register(Context, String) | configure(Context, String) | Renamed to keep parity with iOS implementation | 
| SDKInitialized() | isSDKConfigured() | Renamed for better readability | 
| setIronSourceIntegration() | setIronSourceIntegration() | Direct copy, same signature | 
| setTestMode(boolean) | testModeproperty setter | Converted to property with setter | 
| testLocation(String) | testLocationproperty setter | Converted to property with setter | 
TeadsAdPlacementFeed configurations
| Legacy Outbrain.java Method | New TeadsAdPlacementFeed.kt Method | Notes | 
|---|---|---|
| setDisplayTest(boolean) | testDisplayfield onTeadsAdPlacementFeedConfig | Called internally during Feed placement initialization based on TeadsAdPlacementFeedConfig | 
| handleExploreMore(Activity, Runnable) | handleExploreMore(Activity, Runnable) | Static companion method, direct copy | 
TeadsAdPlacementRecommendations configurations
| Legacy Outbrain.java Method | New TeadsAdPlacementRecommendations.kt Method | Notes | 
|---|---|---|
| getUrl(OBRecommendation) | getUrl(OBRecommendation) | Static companion method, direct copy | 
| getOutbrainAboutURL() | getAdChoicesURL() | Static companion method, direct copy | 
| testRTB(boolean) | setTestRTB(boolean) | Static companion method, renamed for consistency | 
| configureViewabilityPerListingFor(ViewGroup, OBRecommendation) | configureViewabilityPerListingFor(ViewGroup, OBRecommendation) | Static companion method, direct copy | 
| configureViewabilityPerListingFor(OBFrameLayout, OBRecommendation) | configureViewabilityPerListingFor(OBFrameLayout, OBRecommendation) | Static companion method, direct copy | 
New Opportunities with Teads SDK
Enhanced Event Tracking
The unified event system provides more detailed insights:
class ComprehensiveEventHandler : TeadsAdPlacementEventsDelegate {
   override fun onPlacementEvent(
      placement: TeadsAdPlacement,
      event: TeadsAdPlacementEventName,
      data: Map<String, Any>?
   ) {
      Log.d("TeadsSDK", "Event: $event from $placement with data $data")
      when (event) {
         TeadsAdPlacementEventName.LOADED -> {
            // Placement is requested to load the ad
            Log.d("TeadsSDK", "Content fully loaded")
         }
          
         TeadsAdPlacementEventName.READY -> {
            // Ad is loaded and ready to display
            Log.d("TeadsSDK", "Ad is ready to be displayed")
         }
         TeadsAdPlacementEventName.RENDERED -> {
            // Ad has been rendered on screen
            Log.d("TeadsSDK", "Ad has been rendered")
         }
         TeadsAdPlacementEventName.VIEWED -> {
            // Ad has met IAB viewability standards
            Log.d("TeadsSDK", "Ad has met viewability criteria")
         }
         TeadsAdPlacementEventName.CLICKED -> {
            // User clicked on the ad
            val url = data?.get("url") as? String
            Log.d("TeadsSDK", "User clicked on ad: $url")
         }
         TeadsAdPlacementEventName.CLICKED_ORGANIC -> {
            // User clicked on organic content (Feed placement)
            val url = data?.get("url") as? String
            Log.d("TeadsSDK", "User clicked on organic content: $url")
            url?.let {
               // Open in browser or in-app browser
               val intent = Intent(Intent.ACTION_VIEW, Uri.parse(it))
               startActivity(intent)
            }
         }
         TeadsAdPlacementEventName.FAILED -> {
            // Ad failed to load
            // Show fallback content or hide ad container
            val reason = data?.get("reason") as? String
            Log.e("TeadsSDK", "Ad failed to load: $reason")
         }
         TeadsAdPlacementEventName.PLAY -> {
            // Video started playing
            Log.d("TeadsSDK", "Video started playing")
         }
         TeadsAdPlacementEventName.PAUSE -> {
            // Video paused
            Log.d("TeadsSDK", "Video paused")
         }
         TeadsAdPlacementEventName.COMPLETE -> {
            // Video completed
            Log.d("TeadsSDK", "Video completed")
         }
         TeadsAdPlacementEventName.START_PLAY_AUDIO -> {
            // Ad started playing audio
            Log.d("TeadsSDK", "Ad started playing audio")
         }
         TeadsAdPlacementEventName.STOP_PLAY_AUDIO -> {
            // Ad stopped playing audio
            Log.d("TeadsSDK", "Ad stopped playing audio")
         }
         TeadsAdPlacementEventName.HEIGHT_UPDATED -> {
            // Ad height changed (useful for dynamic layouts)
            // This is the height your ad container must match
            val height = data?.get("height") as? Int
            Log.d("TeadsSDK", "Ad height updated to: $height")
         }
         else -> {
            Log.d("TeadsSDK", "Other event: $event")
         }
      }
   }
}
New Placements
Now you can monetize with premium video ads alongside your content recommendations:
Media Placement (Video Ads)
Media placements display premium video advertisements within your content.
import tv.teads.sdk.combinedsdk.adplacement.TeadsAdPlacementMedia
import tv.teads.sdk.combinedsdk.adplacement.config.TeadsAdPlacementMediaConfig
import tv.teads.sdk.combinedsdk.adplacement.interfaces.TeadsAdPlacementEventsDelegate
import tv.teads.sdk.combinedsdk.TeadsAdPlacementEventName
import android.net.Uri
class ContentActivity : AppCompatActivity(), TeadsAdPlacementEventsDelegate {
    private var mediaPlacement: TeadsAdPlacementMedia? = null
    private lateinit var binding: ActivityArticleBinding
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityArticleBinding.inflate(layoutInflater)
        setContentView(binding.root)
        
        setupMediaPlacement()
    }
    private fun setupMediaPlacement() {
        // Create configuration
        val config = TeadsAdPlacementMediaConfig(
            pid = 84242,  // Your unique Placement ID
            articleUrl = Uri.parse("https://yoursite.com/article")
        )
        // Create placement with delegate
        mediaPlacement = TeadsAdPlacementMedia(
           this, // Context
           config, // Placement config
           this // Event delegate
        )
        // Load the ad
        val mediaAdView = mediaPlacement?.loadAd()
        
        // Add to your view hierarchy
       binding.myContainerAdView.addView(mediaAdView)
    }
   
    override fun onPlacementEvent(
       placement: TeadsAdPlacement<*, *>, 
       event: TeadsAdPlacementEventName, 
       data: Map<String, Any>?
    ) {
       // Listen the ad lifecycle events
    }
   override fun onDestroy() {
      super.onDestroy()
      teadsAdPlacementMedia?.clean()
   }
}
Important: When using Media Placement, it's mandatory to set your ad container to dynamic sizing instead of fixed dimensions.
This is because ad creatives served for Media Placement have not a fixed size. The VPAID standard allows them to have dynamic dimensions and aspect ratios, meaning they can change size and shape on the fly.
By using dynamic sizing, you ensure the ad container can automatically resize to perfectly fit the creative's content, preventing issues like cropping or blank spaces. This dynamic resizing is crucial for delivering a great user experience and accurately measuring ad performance throughout the entire ad lifecycle, not just at initialization.
XML Implementation: Use android:layout_height="wrap_content" for your ad container.
Compose Implementation: Use Modifier.wrapContentHeight() or Modifier.height(IntrinsicSize.Min) for your ad container.
Media Native Placement
Media Native placements allow you to create custom layouts for video ads that match your app's design.
import tv.teads.sdk.combinedsdk.adplacement.TeadsAdPlacementMediaNative
import tv.teads.sdk.combinedsdk.adplacement.config.TeadsAdPlacementMediaNativeConfig
import tv.teads.sdk.combinedsdk.adplacement.interfaces.TeadsAdPlacementEventsDelegate
import tv.teads.sdk.combinedsdk.TeadsAdPlacementEventName
import tv.teads.sdk.renderer.NativeAdView
import android.net.Uri
class ContentActivity : AppCompatActivity(), TeadsAdPlacementEventsDelegate {
    private var nativePlacement: TeadsAdPlacementMediaNative? = null
    private var nativeAdView: NativeAdView? = null
    private lateinit var binding: ActivityNativeAdBinding
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityNativeAdBinding.inflate(layoutInflater)
        setContentView(binding.root)
        
        setupNativeAd()
    }
    private fun setupNativeAd() {
        // Create custom native ad view
        nativeAdView = createCustomNativeAdView()
        // Create configuration
        val config = TeadsAdPlacementMediaNativeConfig(
            124859, // Your unique Placement ID
            Uri.parse("https://yoursite.com/article")
        )
        // Create placement
        nativePlacement = TeadsAdPlacementMediaNative(
           this, // Context 
           config, // Placement config
           this // Event delegate
        )
        // Load and bind the ad
        nativePlacement
            ?.loadAd()
            ?.let { binder ->
               // Bind the ad native object to your native ad view
               binder(binding.myContainerAdView.nativeAdView)
           }
    }
    override fun onPlacementEvent(
       placement: TeadsAdPlacement, 
       event: TeadsAdPlacementEventName, 
       data: Map<String, Any>?
    ) {
       // Listen the ad lifecycle events
    }
   override fun onDestroy() {
      super.onDestroy()
      nativePlacement?.clean()
   }
}
Common Questions
Q: Will my existing Outbrain widgets stop working?
A: No, but the legacy Outbrain SDK is being deprecated. However, migration to the Teads Unified SDK preserves all functionality with minimal code changes.
Q: Do I need to change my widget IDs or installation keys?
A: No. All your existing Outbrain configuration parameters (widget IDs, installation keys, etc.) remain the same.
Q: Can I still use the same recommendations UI?
A: Yes, you just need to comply with the new ad request using the placement TeadsAdPlacementRecommendations.
Q: Will my analytics and tracking continue to work?
A: Yes. All existing analytics continue to work, and the new unified event system provides additional insights.
Q: Can I add video ads to my existing app?
A: Yes. The unified SDK gives you access to Teads’ premium video advertising formats alongside your content recommendations, but this need to be arranged with your Publisher Manager.
Migration Checklist
Pre-Migration
- Review current Outbrain SDK implementation
- Identify all widgets and API usage
- Plan migration timeline
- Set up test environment
During Migration
- Update dependencies to Teads SDK v6.x.x
- Replace imports throughout codebase
- Update SDK initialization
- Migrate widget implementations
- Update event handling
- Migrate recommendations API usage
- Update dark mode handling
- Update viewability handling for recommendations API usage
- Test each migrated component
Post-Migration
- Remove Outbrain SDK dependency
- Verify all features work correctly
- Update documentation
- Monitor performance metrics
- Consider adding video ad placements
Support and Resources
Migration Support
- Integration Guide
- Support: Support
Next Steps
- Learn about Privacy Compliance
We're here to help make your migration smooth and successful. Contact our support team with any questions.