# Streambert Wyzie API Key Redemption and Subtitle Downloading: Complete Technical Guide

> Learn how to redeem your Streambert Wyzie API key and download subtitles with this complete technical guide. Securely capture and encrypt credentials for seamless integration.

- Repository: [true_lock/streambert](https://github.com/truelockmc/streambert)
- Tags: how-to-guide
- Published: 2026-05-21

---

**Streambert integrates a built-in Wyzie API key redemption flow that captures free subtitle-service credentials via a secure BrowserWindow, encrypts them using Electron's `safeStorage`, and aggregates search results from both Wyzie and SubDL endpoints.**

The Streambert media player (`truelockmc/streambert`) streamlines subtitle acquisition by automating Wyzie API key redemption directly within the Electron application. This guide examines how the application securely stores these credentials in encrypted form and implements a dual-provider search system that automatically falls back to a public Wyzie endpoint when no key is present. Developers can use these patterns to implement similar OAuth-like redemption flows or troubleshoot subtitle-fetching issues in their own Electron apps.

## How Streambert Handles Wyzie API Key Redemption

The redemption process creates an isolated, non-persistent browser session to prevent credential leakage while monitoring navigation to capture the API key from URL parameters.

### Opening the Secure Redemption Window

The renderer initiates the flow by calling `window.electron.wyzieOpenRedeem()`, exposed in **[`preload.js`](https://github.com/truelockmc/streambert/blob/main/preload.js)** (lines 121–124). This triggers the main process handler registered in **[`index.js`](https://github.com/truelockmc/streambert/blob/main/index.js)**:

```javascript
// index.js – Wyzie redemption window creation
ipcMain.handle("wyzie-open-redeem", async () => {
  return new Promise((resolve) => {
    const { BrowserWindow: BW, session: electronSession } = require("electron");
    const redeemSession = electronSession.fromPartition("partition:wyzie-redeem");
    // ...
    const win = new BW({ 
      // non-persistent session ensures no cached credentials leak
      webPreferences:{ session: redeemSession } 
    });
    // ...
  });
});

```

The implementation creates a dedicated `BrowserWindow` using a partitioned session (`partition:wyzie-redeem`) to ensure complete isolation from the main application context.

### Extracting the Key from Navigation Events

Once the window loads `https://sub.wyzie.io/redeem`, the main process strips CSP headers to ensure proper rendering and monitors navigation via `will-navigate`, `did-navigate`, and `did-navigate-in-page` events. When the URL matches the pattern `https://sub.wyzie.io/notice?key=wyzie-…`, the handler extracts the key parameter:

```javascript
// index.js – URL monitoring logic (lines 20-30)
const checkUrl = (url) => {
  const u = new URL(url);
  if (u.hostname==="sub.wyzie.io" && u.pathname==="/notice") {
     const key = u.searchParams.get("key");
     if (key && key.startsWith("wyzie-")) finish({ok:true, key});
     return true;
  }
  return false;
};

```

The promise resolves with `{ ok:true, key }`, closing the redemption window and returning control to the renderer.

### Encrypting and Storing the Credential

The renderer receives the key and immediately stores it via `window.electron.secureSet("wyzieApiKey", res.key)`, referencing the `STORAGE_KEYS.WYZIE_API_KEY` constant defined in **[`src/utils/storage.js`](https://github.com/truelockmc/streambert/blob/main/src/utils/storage.js)**. This leverages Electron’s `safeStorage` module to encrypt the credential at rest, ensuring the key never touches unencrypted `localStorage` or plain text files.

## Validating Wyzie API Keys Before Use

Before executing subtitle queries, Streambert can verify key validity through a dedicated IPC handler in **[`index.js`](https://github.com/truelockmc/streambert/blob/main/index.js)** (lines 44–56):

```javascript
ipcMain.handle("wyzie-validate-key", async (_, key) => {
  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), 10_000);
  const res = await fetch(
    `https://sub.wyzie.io/search?id=550&format=srt&key=${encodeURIComponent(key)}`,
    { signal: controller.signal }
  ).finally(() => clearTimeout(timer));
  if (res.status===401 || res.status===403) return {ok:false, error:"Invalid or expired key"};
  return {ok:true};
});

```

The validation performs a lightweight search against a static TMDB ID (`550`) with a 10-second timeout. HTTP status codes **401** or **403** trigger an "Invalid or expired key" response, prompting the UI to request redemption.

## Aggregating Subtitles from Wyzie and SubDL

The **IPC subtitle module** ([`src/ipc/subtitles.js`](https://github.com/truelockmc/streambert/blob/main/src/ipc/subtitles.js)) implements a unified `search-subtitles` handler that aggregates results from both providers into a normalized format.

### The IPC Search Handler Architecture

The handler accepts TMDB identifiers, language preferences, and both API keys as parameters (lines 94–112):

```javascript
ipcMain.handle(
  "search-subtitles",
  async (_, { tmdbId, mediaType, season, episode, languages, subdlApiKey, wyzieApiKey }) => {
    // SubDL helper logic omitted for brevity
    
    async function searchWyzie() {
      const params = new URLSearchParams({ id: String(tmdbId), format: "srt" });
      if (languages) params.set("language", languages);
      if (wyzieApiKey) params.set("key", wyzieApiKey);
      
      const baseUrl = wyzieApiKey 
        ? "https://sub.wyzie.io/search" 
        : "https://subs.wyzie.ru/search";
        
      const res = await fetchWithTimeout(`${baseUrl}?${params}`, {}, 12_000);
      // ...
    }
    // ...
  }
);

```

### Endpoint Selection Logic

**When a Wyzie API key is present**, requests route to the authenticated endpoint `https://sub.wyzie.io/search`. **Without a key**, the system falls back to the public endpoint `https://subs.wyzie.ru/search`. SubDL queries execute only when a `subdlApiKey` is supplied, creating a flexible three-tier fallthrough: authenticated Wyzie → public Wyzie → SubDL.

### Normalizing Results for the UI

Each subtitle entry normalizes to a common shape containing `file_id`, `file_name`, `language`, and source flags (`via_wyzie` or `via_subdl`). The UI renders source badges using color logic defined in **[`src/utils/subtitles.js`](https://github.com/truelockmc/streambert/blob/main/src/utils/subtitles.js)** (`sourceBadgeStyle`), allowing users to distinguish between providers visually.

## Fetching and Saving Subtitle Files

Once the user selects a subtitle, Streambert handles provider-specific retrieval methods before saving files adjacent to the video.

### Direct URLs vs ZIP Extraction

The `get-subtitle-url` handler (lines 72–80) differentiates between providers:

- **Wyzie entries**: Already contain a direct download URL. The handler decodes the `file_id` (prefixed with `wyzie_`) and returns the URL immediately.
- **SubDL entries**: Require downloading a ZIP archive, extracting the first subtitle file, and exposing it via a temporary `file://` URI.

```javascript
// src/ipc/subtitles.js – get-url handler excerpt
if (String(fileId).startsWith("wyzie_")) {
  const url = decodeURIComponent(String(fileId).split("_").slice(2).join("_"));
  return { ok:true, url, file_name:"", via_wyzie:true };
}

```

### The Download Workflow

The `downloadSubtitlesForFile` function (lines 33–80) iterates over selected subtitle objects, fetches each file (handling ZIP extraction for SubDL), deduplicates language files to prevent overwriting, and writes them using the naming pattern `<basename>.<lang>.srt`:

```javascript
// src/ipc/subtitles.js – download-for-file excerpt
for (const sub of selectedSubs) {
  // fetch logic varies by provider
  const destPath = path.join(dir, `${baseName}.${langCode}${suffix}.${ext}`);
  fs.writeFileSync(destPath, fileData);
  results.push({ 
    lang: langCode, 
    path: destPath, 
    source: sub.via_subdl ? "subdl" : "wyzie" 
  });
}

```

The function updates the download registry with absolute paths, allowing the renderer to display download status and open containing folders.

## Summary

- **Secure Redemption**: Streambert uses an isolated `BrowserWindow` with a partitioned session to capture Wyzie API keys from navigation events, storing them via Electron `safeStorage` in **[`src/utils/storage.js`](https://github.com/truelockmc/streambert/blob/main/src/utils/storage.js)**.
- **Validation Layer**: The main process validates keys against the Wyzie search endpoint with a 10-second timeout before allowing queries.
- **Dual-Provider Search**: The `search-subtitles` handler in **[`src/ipc/subtitles.js`](https://github.com/truelockmc/streambert/blob/main/src/ipc/subtitles.js)** aggregates results from authenticated Wyzie, public Wyzie, and SubDL endpoints based on available credentials.
- **Provider Abstraction**: Wyzie provides direct URLs while SubDL requires ZIP extraction, both normalized to a common interface for the UI.
- **Local File Integration**: Selected subtitles download directly to the video file’s directory with standardized naming conventions.

## Frequently Asked Questions

### How is the Wyzie API key stored securely?

Streambert encrypts the key using Electron’s `safeStorage` module via the `secureSet` IPC method exposed in **[`preload.js`](https://github.com/truelockmc/streambert/blob/main/preload.js)**. The credential is stored under the `wyzieApiKey` registry entry defined in **[`src/utils/storage.js`](https://github.com/truelockmc/streambert/blob/main/src/utils/storage.js)**, ensuring it never persists in plain text or browser `localStorage`.

### What happens if my Wyzie API key expires?

When executing searches, the validation handler in **[`index.js`](https://github.com/truelockmc/streambert/blob/main/index.js)** detects HTTP 401 or 403 responses from `sub.wyzie.io/search` and returns `{ok:false, error:"Invalid or expired key"}`. The UI can then prompt the user to re-run the redemption flow via `window.electron.wyzieOpenRedeem()` to obtain a fresh key.

### Can I use Streambert subtitles without a Wyzie key?

Yes. The subtitle search logic in **[`src/ipc/subtitles.js`](https://github.com/truelockmc/streambert/blob/main/src/ipc/subtitles.js)** automatically falls back to the public endpoint `https://subs.wyzie.ru/search` when `wyzieApiKey` is null or undefined. Additionally, if you provide a SubDL API key, the application will search that provider regardless of Wyzie authentication status.

### How does Streambert prioritize between SubDL and Wyzie results?

The `search-subtitles` handler executes both searches concurrently (when keys are available) and aggregates results into a single normalized array. The UI displays badges indicating the source (`via_wyzie` or `via_subdl`) via **[`src/utils/subtitles.js`](https://github.com/truelockmc/streambert/blob/main/src/utils/subtitles.js)**, but does not inherently prioritize one over the other; the user selects from the combined list based on language and filename preferences.