{"id":1560,"date":"2026-06-24T09:00:00","date_gmt":"2026-06-24T14:00:00","guid":{"rendered":"https:\/\/tolinku.com\/blog\/?p=1560"},"modified":"2026-03-07T03:58:58","modified_gmt":"2026-03-07T08:58:58","slug":"deep-linking-connected-tv","status":"publish","type":"post","link":"https:\/\/tolinku.com\/blog\/deep-linking-connected-tv\/","title":{"rendered":"Deep Linking for Connected TV Apps"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Connected TV (CTV) apps (Apple TV, Roku, Fire TV, Android TV, smart TV platforms) do not have browsers, cannot open URLs, and cannot click deep links. Yet users constantly navigate between their phones, tablets, and TVs. A user discovers a movie on their phone and wants to play it on the TV. A TV ad shows a QR code that links to a mobile app. A smart TV app needs to authenticate via a phone.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Deep linking for CTV is about bridging these devices. This guide covers the patterns. For QR code strategies, see <a href=\"https:\/\/tolinku.com\/blog\/qr-codes-short-links-mobile-apps\/\">QR codes and short links for mobile apps<\/a>. For cross-device attribution, see <a href=\"https:\/\/tolinku.com\/blog\/cross-device-attribution\/\">cross-device attribution<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><img decoding=\"async\" src=\"https:\/\/tolinku.com\/blog\/wp-content\/uploads\/2026\/03\/connected-tv-streaming.jpeg\" alt=\"Streaming services displayed on smart TV, tablet, and smartphone\">\n<em>Photo by <a href=\"https:\/\/www.pexels.com\/@jakubzerdzicki\" rel=\"nofollow noopener\" target=\"_blank\">Jakub Zerdzicki<\/a> on Pexels<\/em><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">CTV Deep Link Patterns<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Phone-to-TV (Cast\/AirPlay)<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The most common CTV deep link pattern: a user taps a deep link on their phone, and the content plays on the TV.<\/p>\n\n\n\n<pre><code class=\"language-swift\">\/\/ iOS: deep link opens content, user casts to Apple TV\nfunc handleDeepLink(_ url: URL) {\n    let contentId = url.lastPathComponent\n\n    \/\/ Load the content\n    let player = AVPlayer(url: streamURL(for: contentId))\n\n    \/\/ If an AirPlay device is connected, it plays there automatically\n    player.allowsExternalPlayback = true\n    player.play()\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">On Android, use the <a href=\"https:\/\/developers.google.com\/cast\/docs\/android_sender\" rel=\"nofollow noopener\" target=\"_blank\">Cast SDK<\/a>:<\/p>\n\n\n\n<pre><code class=\"language-kotlin\">fun handleDeepLink(uri: Uri) {\n    val contentId = uri.lastPathSegment ?: return\n\n    \/\/ Check if a Cast device is available\n    val castSession = CastContext.getSharedInstance(this)\n        .sessionManager.currentCastSession\n\n    if (castSession != null) {\n        \/\/ Cast to connected TV\n        val mediaInfo = MediaInfo.Builder(contentId)\n            .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)\n            .setContentUrl(getStreamUrl(contentId))\n            .build()\n        castSession.remoteMediaClient?.load(MediaLoadRequestData.Builder().setMediaInfo(mediaInfo).build())\n    } else {\n        \/\/ Play locally\n        playLocally(contentId)\n    }\n}\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">TV-to-Phone (QR Codes)<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">TVs display QR codes that users scan with their phones. This is a deep link from the TV to the phone:<\/p>\n\n\n\n<pre><code>TV displays QR code \u2192 https:\/\/links.yourapp.com\/watch\/movie-123?from=tv&amp;device=living-room\n  \u2192 User scans with phone\n    \u2192 Phone app opens (or web fallback)\n      \u2192 User authenticates or takes action on phone\n        \u2192 TV receives confirmation via websocket\/API\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Use cases:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Authentication:<\/strong> TV shows a QR code; phone scans and logs in (OAuth device flow).<\/li>\n<li><strong>Content exploration:<\/strong> TV shows QR for &quot;learn more&quot;; phone opens a product page.<\/li>\n<li><strong>Purchase:<\/strong> TV shows QR for a product; phone opens the purchase flow.<\/li>\n<li><strong>Social interaction:<\/strong> TV shows QR during a live event; phone opens a chat or poll.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">TV-to-TV (Universal Search)<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Platforms like Apple TV and Android TV have universal search that can deep link into specific apps:<\/p>\n\n\n\n<pre><code class=\"language-swift\">\/\/ tvOS: register for universal search results\nlet searchableItem = CSSearchableItem(\n    uniqueIdentifier: &quot;movie-123&quot;,\n    domainIdentifier: &quot;com.yourapp.movies&quot;,\n    attributeSet: {\n        let attrs = CSSearchableItemAttributeSet(contentType: .movie)\n        attrs.title = &quot;The Great Movie&quot;\n        attrs.contentDescription = &quot;A compelling drama about...&quot;\n        attrs.genre = &quot;Drama&quot;\n        attrs.contentURL = URL(string: &quot;https:\/\/yourapp.com\/watch\/movie-123&quot;)\n        return attrs\n    }()\n)\nCSSearchableIndex.default().indexSearchableItems([searchableItem])\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Device Authentication via Deep Links<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">OAuth Device Code Flow<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The <a href=\"https:\/\/www.rfc-editor.org\/rfc\/rfc8628\" rel=\"nofollow noopener\" target=\"_blank\">OAuth 2.0 Device Authorization Grant<\/a> (RFC 8628) is the standard for authenticating TV apps:<\/p>\n\n\n\n<pre><code>1. TV app requests a device code from your auth server\n2. TV displays: &quot;Go to https:\/\/yourapp.com\/activate and enter code: ABCD-1234&quot;\n3. TV also displays a QR code encoding: https:\/\/yourapp.com\/activate?code=ABCD-1234\n4. User scans QR code with phone \u2192 deep link opens the activation page\n5. User logs in on phone and approves the device\n6. TV polls the auth server and receives an access token\n<\/code><\/pre>\n\n\n\n<pre><code class=\"language-javascript\">\/\/ TV app: start device auth flow\nasync function startDeviceAuth() {\n  const response = await fetch(&#39;https:\/\/api.yourapp.com\/device\/authorize&#39;, {\n    method: &#39;POST&#39;,\n    body: JSON.stringify({ client_id: &#39;tv-app&#39; })\n  });\n\n  const { device_code, user_code, verification_uri, interval } = await response.json();\n\n  \/\/ Display QR code and user code on TV\n  displayQRCode(`${verification_uri}?code=${user_code}`);\n  displayUserCode(user_code);\n\n  \/\/ Poll for authorization\n  pollForToken(device_code, interval);\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The QR code URL is a deep link to your mobile app&#39;s activation screen, pre-filled with the device code.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Content Deep Links for CTV<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">URL Structure<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">CTV content deep links should follow the same URL structure as your mobile and web apps:<\/p>\n\n\n\n<pre><code>\/watch\/{content-id}           \u2192 play specific content\n\/series\/{series-id}           \u2192 series detail page\n\/series\/{series-id}\/s{n}\/e{m} \u2192 specific episode\n\/live\/{channel-id}            \u2192 live channel\n\/search?q={query}             \u2192 search results\n\/profile\/{profile-id}         \u2192 user profile\/watchlist\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Handling Deep Links on tvOS<\/h3>\n\n\n\n<pre><code class=\"language-swift\">\/\/ tvOS AppDelegate\nfunc application(_ application: UIApplication,\n                 continue userActivity: NSUserActivity,\n                 restorationHandler: @escaping ([UIUserActivityRestoring]?) -&gt; Void) -&gt; Bool {\n    guard let url = userActivity.webpageURL else { return false }\n\n    let path = url.pathComponents\n\n    if path.contains(&quot;watch&quot;), let contentId = path.last {\n        playContent(contentId)\n        return true\n    }\n\n    if path.contains(&quot;series&quot;), path.count &gt;= 3 {\n        let seriesId = path[2]\n        showSeriesDetail(seriesId)\n        return true\n    }\n\n    return false\n}\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Handling Deep Links on Android TV<\/h3>\n\n\n\n<pre><code class=\"language-kotlin\">class TVMainActivity : FragmentActivity() {\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n\n        val uri = intent.data\n        if (uri != null) {\n            when {\n                uri.path?.startsWith(&quot;\/watch\/&quot;) == true -&gt; {\n                    val contentId = uri.lastPathSegment\n                    playContent(contentId)\n                }\n                uri.path?.startsWith(&quot;\/series\/&quot;) == true -&gt; {\n                    val seriesId = uri.pathSegments.getOrNull(1)\n                    showSeriesDetail(seriesId)\n                }\n            }\n        }\n    }\n}\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Second Screen Experiences<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Companion App Integration<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">A companion app on the phone provides a second screen experience synced with TV content:<\/p>\n\n\n\n<pre><code class=\"language-javascript\">\/\/ Phone companion app: sync with TV playback\nclass CompanionSync {\n  constructor(tvSessionId) {\n    this.ws = new WebSocket(`wss:\/\/api.yourapp.com\/companion\/${tvSessionId}`);\n\n    this.ws.onmessage = (event) =&gt; {\n      const data = JSON.parse(event.data);\n      switch (data.type) {\n        case &#39;playback_update&#39;:\n          this.showContextualContent(data.contentId, data.timestamp);\n          break;\n        case &#39;scene_change&#39;:\n          this.showSceneInfo(data.sceneData);\n          break;\n        case &#39;interactive_moment&#39;:\n          this.showPoll(data.pollData);\n          break;\n      }\n    };\n  }\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The deep link to start a companion session:<\/p>\n\n\n\n<pre><code>https:\/\/links.yourapp.com\/companion?session=abc123&amp;content=movie-456\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Push Notifications to Phone<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">When something interesting happens on TV, send a deep link notification to the phone:<\/p>\n\n\n\n<pre><code class=\"language-javascript\">\/\/ Server: send companion notification\nasync function notifyCompanion(userId, contentId, action) {\n  await pushService.send(userId, {\n    title: &quot;Now Playing: The Great Movie&quot;,\n    body: &quot;Tap to see behind-the-scenes content&quot;,\n    data: {\n      deepLink: `https:\/\/links.yourapp.com\/companion\/${contentId}?action=${action}`\n    }\n  });\n}\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Tolinku for CTV Deep Links<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/tolinku.com\/features\/deep-linking\">Tolinku<\/a> manages the mobile-side deep link routing for CTV experiences. When a TV displays a QR code linking to a Tolinku-managed URL, the scanned link routes to the mobile app (if installed) or web fallback. Configure your routes in the <a href=\"https:\/\/tolinku.com\/docs\/concepts\/deep-linking\/\">Tolinku dashboard<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For QR code strategies, see <a href=\"https:\/\/tolinku.com\/blog\/qr-codes-short-links-mobile-apps\/\">QR codes and short links for mobile apps<\/a>. For the broader industry trends, see <a href=\"https:\/\/tolinku.com\/blog\/future-mobile-deep-linking\/\">the future of mobile deep linking<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Implement deep linking for connected TV and streaming apps. Handle QR code-to-TV, second screen experiences, and cross-device deep links.<\/p>\n","protected":false},"author":2,"featured_media":1559,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"rank_math_title":"Deep Linking for Connected TV Apps","rank_math_description":"Implement deep linking for connected TV and streaming apps. Handle QR code-to-TV, second screen, and cross-device deep links.","rank_math_focus_keyword":"deep linking connected TV","rank_math_canonical_url":"","rank_math_facebook_title":"","rank_math_facebook_description":"","rank_math_facebook_image":"https:\/\/tolinku.com\/blog\/wp-content\/uploads\/2026\/03\/og-deep-linking-connected-tv.png","rank_math_facebook_image_id":"","rank_math_twitter_title":"","rank_math_twitter_description":"","rank_math_twitter_image":"https:\/\/tolinku.com\/blog\/wp-content\/uploads\/2026\/03\/og-deep-linking-connected-tv.png","footnotes":""},"categories":[11],"tags":[425,341,20,69,48,426,294,427],"class_list":["post-1560","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-deep-linking","tag-connected-tv","tag-cross-device","tag-deep-linking","tag-mobile-development","tag-qr-codes","tag-second-screen","tag-streaming","tag-tvos"],"_links":{"self":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts\/1560","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/comments?post=1560"}],"version-history":[{"count":4,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts\/1560\/revisions"}],"predecessor-version":[{"id":2745,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts\/1560\/revisions\/2745"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/media\/1559"}],"wp:attachment":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/media?parent=1560"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/categories?post=1560"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/tags?post=1560"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}