YouTube’s BotGuard system requires a Proof-of-Origin Token (PoToken) for certain YouTube clients. The token proves that a genuine browser environment generated the request rather than an automated tool. In the InnerTube SDK this proof is computed locally by running Google’s BotGuard JavaScript inside an Android WebView — no external server is involved. TwoDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/faraasaaay/innertube-v1/llms.txt
Use this file to discover all available pages before exploring further.
YouTubeClient flags control whether a PoToken is needed:
| Flag | Meaning |
|---|---|
useWebPoTokens = true | The client accepts a PoToken and will embed it in serviceIntegrityDimensions if one is provided |
requirePoToken = true | The client will fail without a valid PoToken (set only on TVHTML5_SIMPLY) |
useWebPoTokens = true include WEB_REMIX, WEB_CREATOR, TVHTML5, and TVHTML5_SIMPLY.
PoTokenGenerator
PoTokenGenerator manages the Android WebView lifecycle for token generation. Instantiate it once and reuse it throughout the application lifetime.
getWebClientPoToken(videoId, sessionId)
This is the primary method. It returns a PoTokenResult? — null if the WebView is unavailable or generation times out.
visitorData (the session ID) must be set on YouTube.visitorData before calling getWebClientPoToken. The streaming PoToken is generated from sessionId and must be obtained first — the generator enforces this ordering automatically when it creates a new WebView instance.PoTokenResult
PoTokenResult carries two distinct tokens returned from a single generator call:
| Field | Type | Description |
|---|---|---|
playerRequestPoToken | String | Session-level token derived from sessionId. Generated once per session and reused across multiple player calls with the same visitorData. This is the value passed to YouTube.player() as the poToken argument. |
streamingDataPoToken | String | Per-video token derived from videoId. Appended to adaptive stream URLs as the pot= query parameter by YTPlayerUtils during URL resolution. |
Lifecycle management
PoTokenGenerator maintains a single internal PoTokenWebView instance. A new WebView is created (and the old one closed) when any of the following conditions are detected at the start of getWebClientPoToken:
First call
webPoTokenGenerator == null — no WebView exists yet.Session changed
webPoTokenSessionId != sessionId — the visitorData has rotated (e.g. after sign-in or sign-out).Token expired
webPoTokenGenerator.isExpired — the BotGuard token lifetime has elapsed.Renderer killed
webPoTokenGenerator.isDead — the WebView renderer process was killed by the OS (low-memory event).- Closes the old WebView (
PoTokenWebView.close()hops to the Main dispatcher). - Clears the cached
webPoTokenStreamingPotandwebPoTokenSessionId. - Creates a fresh
PoTokenWebViewviaPoTokenWebView.getNewPoTokenGenerator(context). - Generates a streaming PoToken from
sessionIdbefore any per-video tokens are produced.
Timeout handling
PoToken generation is guarded by an 8-second timeout (POTOKEN_TIMEOUT_MS = 8_000L). This accounts for the cold-start cost of WebView spin-up and BotGuard JavaScript execution (~2–5 seconds on a healthy device) while leaving a margin for slow hardware before the fallback chain takes over.
When the timeout fires:
- The generator logs a warning and closes the current WebView.
- All cached state (
webPoTokenGenerator,webPoTokenStreamingPot,webPoTokenSessionId) is cleared under the mutex. getWebClientPoTokenreturnsnull.YTPlayerUtils.playerResponseForPlaybackfalls through to non-PoToken clients such asANDROID_VR.
BadWebViewException
If the device’s system WebView implementation is broken or incompatible,PoTokenWebView.getNewPoTokenGenerator() throws a BadWebViewException. When PoTokenGenerator catches this exception it sets the internal webViewBadImpl = true flag, and all subsequent calls to getWebClientPoToken return null immediately without attempting to spin up a WebView again.
Pre-warming the generator
YTPlayerUtils.prewarmPoToken() warms the generator at app start using a stable dummy video ID ("jNQXAC9IVRw"). Because PoToken generation is a local WebView computation with no YouTube network request for the warm-up video itself, this is cheap to call. The result is discarded — the goal is simply to have the WebView and BotGuard JS already loaded when the user first taps play.
Integration with the player
When a client hasuseWebPoTokens = true, the playerRequestPoToken is embedded in the player request body as serviceIntegrityDimensions.poToken. The InnerTube.player() method handles this automatically when you pass a non-null poToken argument. The streamingDataPoToken is separately appended to stream URLs as pot= by YTPlayerUtils during URL resolution.