Overview

WebSocket API

Complete reference for the WebSocket message protocol between frontend and backend

Connection

The WebSocket server runs on ws://localhost:44030/ and accepts connections from the frontend.

const socket = new WebSocket('ws://localhost:44030/');

The backend only allows one active WebSocket connection. New connections automatically close previous ones.

The server checks the Origin header of each upgrade request. Requests whose Origin host is not localhost or 127.0.0.1 are rejected with HTTP 403. Requests without an Origin header are accepted.

Segra also runs a separate WebSocket endpoint on ws://localhost:44031/ (Windows only, Backend/App/AudioStreamServer.cs) that receives in-app playback audio from the webview as raw PCM. It does not carry the JSON message protocol documented on this page.

Message Structure

Frontend to Backend

Messages sent via window.external.sendMessage() use this JSON structure:

{
  "Method": "string",
  "Parameters": {
    // Method-specific parameters
  }
}

Backend to Frontend

Messages received via WebSocket use this JSON structure:

{
  "method": "string",
  "content": {
    // Event-specific data
  }
}

Note the casing difference: Method (capitalized) for frontend-to-backend, method (lowercase) for backend-to-frontend due to JSON serialization settings.

Frontend to Backend Methods

These commands can be sent from the frontend to control the backend.

Recording Control

Starts a manual recording session.

Parameters: None

Example:

sendMessageToBackend('StartRecording');

Stops the current recording.

Parameters: None

Example:

sendMessageToBackend('StopRecording');

Authentication

Loginmethod

Authenticates the user with Supabase tokens.

Parameters:

accessTokenstringrequired

Supabase access token from the session

refreshTokenstringrequired

Supabase refresh token for token renewal

Example:

sendMessageToBackend('Login', {
  accessToken: session.access_token,
  refreshToken: session.refresh_token,
});
Logoutmethod

Signs out the current user.

Parameters: None

Example:

sendMessageToBackend('Logout');

Clip Creation

Creates clips from selected video segments.

Parameters:

Optional. Set to "separate" to export each segment as its own clip instead of concatenating them.

Segmentsarrayrequired

Array of segment objects defining clip ranges. Segments no longer duplicate content metadata; the backend resolves game, title, type, IGDB ID, file name, and file path from the referenced content.

idnumberrequired

Unique identifier for this segment

contentIdstringrequired

ID of the source content (from Content.id). The backend looks up the source file, type, game, title, and IGDB ID from application state.

startTimenumberrequired

Start timestamp in seconds

endTimenumberrequired

End timestamp in seconds

Optional array of audio track indexes to mute in the output

Optional map of { "<trackIndex>": <volume> } to apply per-track volume adjustments

Example:

sendMessageToBackend('CreateClip', {
  Segments: [{
    id: 1,
    contentId: "9f1c8d0a-...",
    startTime: 30.5,
    endTime: 45.2,
  }]
});

Cancels an in-progress clip operation.

Parameters:

idnumberrequired

The clip operation ID to cancel

Example:

sendMessageToBackend('CancelClip', { id: 123 });

Joins whole videos, in the given order, into one new video. When every ID is a session, the backend merges them into a new session by stream copy and fails with the error These sessions can't be merged without re-encoding if their formats differ. Otherwise the result is a new clip. Its video is copied when all videos share identical encoder headers and re-encoded with the clip settings when they don't. Progress is reported through ClipProgress.

Parameters:

Idsstring[]required

IDs of the videos to merge (from Content.id), in output order.

Example:

sendMessageToBackend('MergeContent', {
  Ids: ["9f1c8d0a-...", "4b7e2f13-..."]
});

Asks whether the given videos can be merged without re-encoding. The backend replies with a MergeCheck event.

Parameters:

Idsstring[]required

IDs of the videos to check (from Content.id).

Example:

sendMessageToBackend('CheckMerge', {
  Ids: ["9f1c8d0a-...", "4b7e2f13-..."]
});

Creates AI-generated highlights from a video.

Parameters:

Idstringrequired

ID of the source content to process (from Content.id).

Example:

sendMessageToBackend('CreateAiClip', {
  Id: "9f1c8d0a-..."
});

Content Management

Deletes a single piece of content.

Parameters:

Idstringrequired

ID of the content to delete (from Content.id). The backend resolves the file path and content type from application state.

Example:

sendMessageToBackend('DeleteContent', {
  Id: "9f1c8d0a-..."
});

Deletes multiple content items in a batch operation.

Parameters:

Idsstring[]required

Array of content IDs to delete (from Content.id).

Example:

sendMessageToBackend('DeleteMultipleContent', {
  Ids: [
    "9f1c8d0a-...",
    "b2ac7e11-..."
  ]
});

Renames a content item's title.

Parameters:

Idstringrequired

ID of the content to rename (from Content.id).

Titlestringrequired

New title for the content.

Example:

sendMessageToBackend('RenameContent', {
  Id: "9f1c8d0a-...",
  Title: "Ace on Ascent"
});

Imports an external video file into Segra.

Parameters: Handled by ImportService.HandleImportFile

Example:

sendMessageToBackend('ImportFile', {
  // Parameters defined in ImportService
});

Upload & Compression

Uploads content to Segra.tv. The backend reads Game and IgdbId off the referenced content and uses the current session for authentication (no JWT field is sent).

Parameters:

Idstringrequired

ID of the content to upload (from Content.id).

Titlestringrequired

Title to display on Segra.tv.

Optional description for the upload.

"Public" or "Unlisted".

Example:

sendMessageToBackend('UploadContent', {
  Id: "9f1c8d0a-...",
  Title: "Epic Ace",
  Description: "1v5 clutch on Ascent",
  Visibility: "Public"
});

Cancels an in-progress upload.

Parameters:

uploadIdstringrequired

ID of the upload to cancel (from UploadProgress.uploadId). Earlier builds took a fileName instead.

Example:

sendMessageToBackend('CancelUpload', {
  uploadId: "3f2b9c1e8a4d4f6b9e0c7d5a1b2c3d4e"
});

Compresses a video to reduce file size. The compressed file inherits the source's name (falling back to " (1)", " (2)", ... on collision) and is flagged in metadata via Content.compressed; there is no longer a _compressed filename suffix.

Parameters:

Idstringrequired

ID of the content to compress (from Content.id).

Example:

sendMessageToBackend('CompressVideo', {
  Id: "9f1c8d0a-..."
});

Bookmarks

Adds a bookmark to a piece of content.

Parameters:

ContentIdstringrequired

ID of the content to bookmark (from Content.id).

Idnumberrequired

Client-generated bookmark ID.

Typestringrequired

Bookmark type (parsed as BookmarkType).

Timestringrequired

Timestamp string parsed by the backend (e.g. "00:01:23.500").

Example:

sendMessageToBackend('AddBookmark', {
  ContentId: "9f1c8d0a-...",
  Id: 1,
  Type: "Manual",
  Time: "00:01:23.500"
});

Removes a bookmark from a piece of content.

Parameters:

ContentIdstringrequired

ID of the content the bookmark belongs to (from Content.id).

Idnumberrequired

Bookmark ID to remove.

Example:

sendMessageToBackend('DeleteBookmark', {
  ContentId: "9f1c8d0a-...",
  Id: 1
});

Settings

Updates application settings.

Parameters: Handled by SettingsService.HandleUpdateSettings

If the request sets codec to a codec that isn't available on this system, the backend ignores the codec change. If a preset changes the encoder, the backend also switches the codec to that encoder's default. Changes to recording settings made during a recording apply from the next recording.

Example:

sendMessageToBackend('UpdateSettings', {
  // Settings object with changed values
});

Opens a folder picker to change the video storage location.

Parameters: None

Example:

sendMessageToBackend('SetVideoLocation');

Opens a folder picker to change the cache storage location.

Parameters: None

Example:

sendMessageToBackend('SetCacheLocation');

Applies a video quality preset (see PresetsService.ApplyVideoPreset).

Parameters:

presetstringrequired

Preset name: low, standard, high, or custom

Example:

sendMessageToBackend('ApplyVideoPreset', {
  preset: "high"
});

Applies a clip quality preset (see PresetsService.ApplyClipPreset).

Parameters:

presetstringrequired

Preset name: low, standard, high, or custom

Example:

sendMessageToBackend('ApplyClipPreset', {
  preset: "standard"
});

Game Management

Per-game record/skip preferences live on the unified games array in Settings.Instance (each entry has a record boolean plus optional quality / recording-mode overrides). Update them by sending an UpdateSettings message with the modified games array — there are no dedicated AddToWhitelist / AddToBlacklist / MoveGame methods. The legacy whitelist / blacklist fields are migration-only and may be removed in a future release.

Opens a file picker to select a game executable.

Parameters: None

Response: Backend sends SelectedGameExecutable message with the selected file. If you cancel the picker, paths is an empty array.

Example:

sendMessageToBackend('SelectGameExecutable');

System Actions

Opens Windows Explorer to the file location.

Parameters:

FilePathstringrequired

Full path to the file

Example:

sendMessageToBackend('OpenFileLocation', {
  FilePath: "C:/Videos/clip.mp4"
});

Copies a file to the Windows clipboard so it can be pasted into Explorer, chat clients, or other apps that accept file drops.

Parameters:

FilePathstringrequired

Full path to the file to copy

Example:

sendMessageToBackend('CopyFileToClipboard', {
  FilePath: "C:/Videos/clip.mp4"
});

Compresses a video down to a target size and copies the result to the Windows clipboard. Powers the "Copy as {size} MB" entries in the video page's Copy split button.

Parameters:

FilePathstringrequired

Full path to the source video file.

MaxSizeMbnumberrequired

Target maximum size for the compressed copy, in megabytes. Values that are not smaller than the source are rejected by the UI. See copyCompressSizesMb in Settings for the sizes offered by default.

Response: Backend streams ClipboardCompressionProgress events during compression.

Example:

sendMessageToBackend('CopyCompressedFileToClipboard', {
  FilePath: "C:/Videos/clip.mp4",
  MaxSizeMb: 50
});

Opens a URL in the default browser.

Parameters:

Urlstringrequired

URL to open

Example:

sendMessageToBackend('OpenInBrowser', {
  Url: "https://segra.tv"
});

Opens Windows Explorer to the logs folder.

Parameters: None

Example:

sendMessageToBackend('OpenLogsLocation');

Toggles fullscreen mode.

Parameters:

enabledbooleanrequired

True to enable fullscreen, false to disable

Example:

sendMessageToBackend('ToggleFullscreen', {
  enabled: true
});

Updates

Checks for available application updates.

Parameters: None

Example:

sendMessageToBackend('CheckForUpdates');

Applies a downloaded update and restarts the application.

Parameters: None

Example:

sendMessageToBackend('ApplyUpdate');

Connection

Signals that a new WebSocket connection has been established. The backend responds with initial state.

Parameters: None

Response: Backend sends:

  • Settings with persisted settings
  • State with runtime application state
  • GameList with available games
  • AppVersion with backend version (only when an update manager build is available)
  • UpdateProgress if an update download/install was in flight when the client (re)connected, replaying the most recent progress payload
  • ReleaseNotes asynchronously, after fetching from the update channel

Example:

sendMessageToBackend('NewConnection');

Storage & Recovery

Confirms or cancels a storage warning action. The frontend sends confirmed: false when you cancel or dismiss the warning modal.

Parameters: Handled by StorageWarningService.HandleStorageWarningConfirm

sendMessageToBackend('StorageWarningConfirm', {
  warningId: '...',
  confirmed: false,
  action: 'import',
  actionData: { ... },
});

Confirms recovery of temporary files after a crash.

Parameters: Handled by RecoveryService.HandleRecoveryConfirm

Linux Wayland only. Clears the saved screen-share choice (pipeWireRestoreToken) so the next display capture opens the screen-share dialog again. The backend responds with an updated Settings message.

Pauses or resumes global hotkeys while the Settings page captures a new binding. A pause expires after one minute if the frontend never resumes.

sendMessageToBackend('SetHotkeysPaused', { paused: true });

Windows only. Downloads and installs Microsoft's HEVC Video Extension so H.265 videos can play in Segra. The backend sends HevcExtensionInstallFinished with { success: boolean } when done.

Windows only. Restarts Segra. The frontend sends it after the HEVC Video Extension is installed.

Triggers migration of existing content into Segra's canonical folder layout.

Parameters: Handled by ContentMigrationService.HandleMigrateContent

Progress: The backend streams ContentMigrationProgress events while the migration runs.


Backend to Frontend Events

These messages are sent from the backend to update the frontend state.

State Updates

Sends the persisted user settings to the frontend. content is the bare Settings.Instance object (no wrapping settings key).

Example:

{
  "method": "Settings",
  "content": {
    "contentFolder": "C:/Videos",
    "cacheFolder": "C:/AppData/Segra",
    "resolution": "1440p",
    "frameRate": 60,
    "recordingMode": "Hybrid",
    "keybindings": [...]
    // ... all persisted settings
  }
}

Runtime state (active recording, content library, detected devices, current folder size, etc.) is no longer nested under settings.state. It is delivered separately via the State message described below.

Stateevent

Sends the runtime AppState.Instance object to the frontend. This is non-persisted, in-memory state that mirrors the live application status.

Content (selected fields):

recordingobject

The active Recording (or null when idle). Includes startSettings, a snapshot of the recording settings the recording started with (recordingMode, resolution, frameRate, rate control and bitrate fields, encoder, codec, replayBufferDuration, replayBufferMaxSize, inputDevices, outputDevices, enableSeparateAudioTracks, audioOutputMode, gameIntegrations, and more). The Settings page compares this snapshot with the current settings to show (applies to next recording).

True while the always-on replay buffer is running (see alwaysOnReplayBuffer in settings)

A PreRecording placeholder while Segra is preparing to record (or null)

True while Segra post-processes a finished session (metadata, thumbnail, waveform). A new recording can start during this time.

True on Linux Wayland sessions, where the desktop's screen-share dialog picks the captured screen instead of the monitor setting

True on Linux Wayland when Segra can't read keyboards in /dev/input, so hotkeys only work while an X11 window is focused. Settings shows a warning with sudo usermod -aG input $USER.

contentarray

All known content items (sessions, buffers, clips, highlights)

Detected audio input devices

Detected audio output devices

Detected monitors. Each display includes deviceId, deviceName, isPrimary, isHdr, and its current resolution (width, height)

codecsarray

Available video codecs/encoders

OBS versions known to the updater

gpuVendorstring

Detected GPU vendor (Nvidia, Amd, Intel, or Unknown)

Detected CUDA compute capability for NVIDIA GPUs

Maximum height of any connected display (used for resolution clamping)

True once OBS has been initialized

True while an update check is in-flight

Total size of the content folder in GB

Example:

{
  "method": "State",
  "content": {
    "recording": null,
    "preRecording": null,
    "content": [],
    "gpuVendor": "Nvidia",
    "currentFolderSizeGb": 12.4,
    "hasLoadedObs": true
  }
}

Sends the list of detected games.

Content: The content field is itself an array of GameEntry objects — there is no wrapping games key.

Example:

{
  "method": "GameList",
  "content": [
    {
      "name": "Valorant",
      "executables": ["C:\\Games\\Valorant\\VALORANT.exe"]
    }
  ]
}

Each entry uses the GameEntry shape from Backend/Games/GameUtils.cs — name and executables (back-slashed). It is distinct from the Game shape used for whitelist/blacklist commands, which uses paths.

Sends the backend application version for version mismatch detection.

Content:

versionstring

Semantic version string (e.g., "1.2.3")

Example:

{
  "method": "AppVersion",
  "content": {
    "version": "1.2.3"
  }
}

Frontend automatically reloads if this doesn't match __APP_VERSION__

Progress Events

Reports progress of clip creation operations.

Content:

idnumberrequired

Clip operation ID

progressnumberrequired

Progress percentage (0-100), or -1 for error

segmentsarrayrequired

Array of segments being processed (mirrors what was sent in CreateClip)

errorstring

Error message if progress is -1. When FFmpeg fails, this is the first error line FFmpeg printed, or a description of the exit code if it printed none.

Set to "Session" when the operation is a session merge from MergeContent. Omitted for clips.

Example:

{
  "method": "ClipProgress",
  "content": {
    "id": 123456,
    "progress": 45,
    "segments": [...]
  }
}

Reports progress of upload operations.

Content:

uploadIdstringrequired

Unique ID generated for this upload. The same file can be uploaded more than once at a time, so each upload gets its own ID. Pass it to CancelUpload.

fileNamestringrequired

File being uploaded

titlestringrequired

Title submitted with the upload (mirrored in every progress event)

progressnumberrequired

Progress percentage (0-100), or -1 for error

statusstringrequired

Upload status: "uploading", "processing", "done", or "error"

messagestring

Status message or error description

Local file path of the source content's thumbnail (from FolderNames.GetThumbnailFilePath). Used by upload cards to render a blurred background matching the content's cover. Omitted when no thumbnail exists.

Example:

{
  "method": "UploadProgress",
  "content": {
    "uploadId": "3f2b9c1e8a4d4f6b9e0c7d5a1b2c3d4e",
    "fileName": "clip_001.mp4",
    "progress": 75,
    "status": "uploading",
    "thumbnailPath": "C:/Users/User/AppData/Roaming/Segra/thumbnails/...jpeg",
    "message": "Uploading to server..."
  }
}

Reports progress of file import operations. Recovering an orphaned file also reports progress through this event.

Content:

idnumberrequired

Import operation ID

actionstring

Set to "recover" when the progress is for a file recovery. Omitted for imports.

fileNamestringrequired

Current file being imported

progressnumberrequired

Progress percentage for current file

statusstringrequired

Import status: "importing", "done", or "error"

totalFilesnumberrequired

Total number of files to import

currentFileIndexnumberrequired

Index of current file (0-based)

messagestring

Status message

Reply to CheckMerge.

Content:

idsstring[]required

The IDs from the request

fastbooleanrequired

true when the videos can be merged without re-encoding. Sessions must also have matching audio formats and audio track names.

Reports progress of video compression.

Content:

filePathstringrequired

File being compressed

progressnumberrequired

Progress percentage (0-100), or -1 for error

statusstringrequired

Status: "compressing", "done", "skipped", or "error"

messagestring

Status message

Example:

{
  "method": "CompressionProgress",
  "content": {
    "filePath": "C:/Videos/large.mp4",
    "progress": 50,
    "status": "compressing"
  }
}

Reports progress of a CopyCompressedFileToClipboard operation. The video page uses these events to animate the Copy button while a size-capped copy is being prepared.

Content:

filePathstringrequired

Source file being compressed for the clipboard copy

progressnumberrequired

Progress percentage (0-100)

statusstringrequired

Status: "compressing", "done", or "error"

messagestring

Status or error message

Reports progress of application updates.

Content:

versionstringrequired

Version being downloaded

progressnumberrequired

Download progress (0-100)

statusstringrequired

Status: "downloading", "downloaded", or "ready"

messagestringrequired

Human-readable status message

Reports progress of AI highlight generation.

Content: Handled by AiService

Indicates whether the recording-preview window is currently active (the in-app live preview controlled by the TogglePreview keybind).

Content:

enabledbooleanrequired

Whether preview rendering is active

Streams a frame of the live recording preview to the frontend (sent only while the preview is enabled).

Content: A frame payload emitted by RecordingPreviewService.

Reports status of data migration operations.

Content:

isRunningbooleanrequired

Whether migration is currently running

Description of current migration

Reports progress of a content-folder migration triggered by MigrateContent.

Content:

idstringrequired

Migration operation ID

fileNamestringrequired

File currently being moved

progressnumberrequired

Progress percentage (0-100), or -1 for error

statusstringrequired

Migration status (e.g. "migrating", "done", "error")

Total number of files in the migration

Index of the file currently being moved

messagestring

Status or error message

Fired when the user saves the replay buffer (typically via the SaveReplayBuffer keybind). Emitted with an empty content payload — frontends usually surface a toast or sound effect in response.

User Notifications

Displays a modal dialog to the user.

Content:

titlestringrequired

Modal title

subtitlestring

Optional subtitle

descriptionstringrequired

Modal body text

typestringrequired

Modal type: "info", "warning", or "error"

Example:

{
  "method": "ShowModal",
  "content": {
    "title": "Error",
    "description": "Failed to start recording",
    "type": "error"
  }
}

Warns user about storage space issues and requests confirmation.

Content:

warningIdstringrequired

Unique ID for this warning

titlestringrequired

Warning title

descriptionstringrequired

Warning description

confirmTextstringrequired

Text for confirm button

cancelTextstringrequired

Text for cancel button

actionstringrequired

Action to perform if confirmed

actionDataanyrequired

Data needed to perform the action

Prompts user to recover files after a crash.

Content:

filesarrayrequired

Array of recoverable file objects

totalCountnumberrequired

Total number of files that can be recovered

Notifies that a bookmark was created (typically from a keybind).

Content: Empty object

Example:

{
  "method": "BookmarkCreated",
  "content": {}
}

UI Events

Returns the game executable selected by the user.

Content:

namestringrequired

Game name (derived from executable name)

pathsarrayrequired

Array containing the selected executable path. Empty (and name omitted) when you cancel the picker.

Example:

{
  "method": "SelectedGameExecutable",
  "content": {
    "name": "MyGame",
    "paths": ["C:/Games/MyGame/game.exe"]
  }
}

Sends release notes for available versions.

Content:

releaseNotesListarrayrequired

Array of release note objects

versionstringrequired

Version number

base64Markdownstringrequired

Release notes in base64-encoded markdown

releaseDatestringrequired

ISO 8601 date string

Triggers the frontend to display release notes.

Content: Version string to show notes for

Reports OBS download progress during first-time setup.

Content:

progressnumberrequired

Download progress (0-100)

statusstringrequired

Download status: "downloading"

Heartbeat

pongevent

Response to ping heartbeat message.

Content: Empty object

Example:

{
  "method": "pong",
  "content": {}
}

Error Handling

Frontend Validation

Always check if the backend connection is available before sending messages:

if ((window as any).external && typeof (window as any).external.sendMessage === 'function') {
  sendMessageToBackend('StartRecording');
} else {
  console.error('Backend connection not available');
}

Backend Error Responses

When operations fail, the backend typically:

  1. Logs the error with Serilog
  2. Sends a progress event with progress: -1 and an error field
  3. Optionally shows a ShowModal message to the user

Example error response:

{
  "method": "ClipProgress",
  "content": {
    "id": 123456,
    "progress": -1,
    "segments": [...],
    "error": "FFmpeg not found. Please reinstall Segra."
  }
}

Connection Failures

The WebSocket automatically reconnects if the connection drops:

  • Reconnect attempts: Infinite
  • Reconnect interval: 3 seconds
  • Heartbeat timeout: 120 seconds without any message from the backend

When reconnected, the backend re-sends the full state:

ws.onopen = () => {
  console.log('WebSocket connected');
  sendMessageToBackend('NewConnection');
};

Best Practices

Use TypeScript Types

Import message type definitions from Models/WebSocketMessages.ts for type safety.

Handle All States

Progress events can be: in-progress (0-100), complete (100), or error (-1). Handle all cases.

Listen to Events

Use window.addEventListener('websocket-message') to react to backend events globally.

Graceful Degradation

Always check if window.external.sendMessage exists before calling it.

Never assume the WebSocket is connected. Use the isConnected state from useWebSocketContext() to check connection status.

Example: Complete Clip Workflow

Here's a complete example showing the clip creation workflow:

import { sendMessageToBackend } from '../Utils/MessageUtils';
import { useEffect, useState } from 'react';

function ClipCreator() {
  const [progress, setProgress] = useState(0);
  const [clipId, setClipId] = useState<number | null>(null);

  // Listen for progress updates
  useEffect(() => {
    const handleMessage = (event: CustomEvent) => {
      const { method, content } = event.detail;
      
      if (method === 'ClipProgress') {
        if (content.id === clipId) {
          if (content.progress === -1) {
            console.error('Clip failed:', content.error);
          } else if (content.progress === 100) {
            console.log('Clip complete!');
          } else {
            setProgress(content.progress);
          }
        }
      }
    };

    window.addEventListener('websocket-message', handleMessage as EventListener);
    return () => window.removeEventListener('websocket-message', handleMessage as EventListener);
  }, [clipId]);

  const createClip = () => {
    const id = Math.floor(Math.random() * 1000000);
    setClipId(id);
    
    sendMessageToBackend('CreateClip', {
      Segments: [{
        id: id,
        contentId: sourceContent.id,
        startTime: 10,
        endTime: 20,
      }]
    });
  };

  const cancelClip = () => {
    if (clipId) {
      sendMessageToBackend('CancelClip', { id: clipId });
    }
  };

  return (
    <div>
      <button onClick={createClip}>Create Clip</button>
      <button onClick={cancelClip}>Cancel</button>
      <progress value={progress} max={100} />
    </div>
  );
}

Next Steps