Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/ZemerTeam/zemer-cipher/llms.txt

Use this file to discover all available pages before exploring further.

YouTube’s web clients (WEB, WEB_REMIX) require a proof-of-origin token — a PoToken — generated by Google’s BotGuard system. Without valid PoTokens, CDN delivery is restricted: the first request succeeds normally, but the stream is cut off or degraded after approximately the first 1 MiB of data. Zemer Cipher generates PoTokens by running the BotGuard challenge inside an Android WebView, producing tokens that are structurally identical to those the official YouTube web player would generate.

Two Tokens, Two Purposes

A complete PoTokenResult carries two distinct tokens, each bound to a different identity: playerRequestPoToken — bound to the session (visitorData). This token is minted once per session and reused across multiple videos in that session. It is sent in the InnerTube /player request body, alongside the video ID and client information. YouTube validates it server-side before returning stream metadata. streamingDataPoToken — bound to the video ID. This token must be appended to each CDN stream URL as pot=<token>. The CDN enforces the video binding: a streamingDataPoToken that was minted for a different video, or a playerRequestPoToken used in its place, will cause the CDN to serve only the first ~1 MiB before stopping delivery.
val result: PoTokenResult = generator.getWebClientPoToken(videoId, sessionId)

// Send in the /player InnerTube request body:
val playerPoToken = result.playerRequestPoToken

// Append to each CDN stream URL as pot=:
val streamPoToken = result.streamingDataPoToken

How Generation Works

  1. A PoTokenWebView is created by calling PoTokenWebView.getNewPoTokenGenerator(context). It loads the bundled po_token.html asset, which contains the BotGuard JavaScript challenge.
  2. On first use (or when the sessionId changes), a session token is minted by calling generatePoToken(sessionId). The sessionId is the visitorData string from a prior InnerTube response. This session token becomes playerRequestPoToken and is reused for all subsequent videos in the same session.
  3. For each video, generatePoToken(videoId) mints a video-bound token that becomes streamingDataPoToken. This call is made once per video and is not cached — each video needs its own token.
  4. Both tokens are assembled into a PoTokenResult and returned to the caller.

WebView Lifecycle

PoTokenGenerator holds a single PoTokenWebView instance and reuses it across calls. A new WebView is created only when necessary:
ConditionAction
First callCreate and initialize a new WebView
sessionId changedClose old WebView, create a new one, re-mint session token
isExpired is trueSame as above — WebView has exceeded its maximum age
isDead is trueRenderer was killed (OOM); recreate before the next call
The timeout budget for the entire operation — WebView spin-up, BotGuard JS execution, and token generation — is 8 seconds. Cold-start latency in practice is 2–5 seconds on a mid-range device. Warm-path token mints (reusing an existing WebView) are sub-second. If the timeout is exceeded, the generator closes the current WebView, clears all session state, and returns null. The caller is expected to fall through to a non-web client that does not require PoTokens.

Fallback Behavior

PoToken generation may be unavailable in two situations:
  • webViewSupported = falseCookieManager.getInstance() threw during initialization, indicating that WebView is not functional on this device (missing system component, TV OS, etc.).
  • BadWebViewException — the WebView was present but behaved incorrectly. This flag is latched: once set, getWebClientPoToken() returns null immediately on all future calls without attempting WebView creation.
In both cases, getWebClientPoToken() returns null. Callers should treat a null result as a signal to fall through to an Android-native client such as ANDROID_VR or IOS, which do not require PoTokens and are not subject to the same CDN restrictions.
PoToken generation is only required for WEB and WEB_REMIX client streams. Android-native client streams — ANDROID, ANDROID_VR, IOS — do not need PoTokens and will play normally without them.

PoToken generation patterns based on BgUtils (MIT License).

Build docs developers (and LLMs) love