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
Authenticates the user with Supabase tokens.
Parameters:
Supabase access token from the session
Supabase refresh token for token renewal
Example:
sendMessageToBackend('Login', {
accessToken: session.access_token,
refreshToken: session.refresh_token,
});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.
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.
Unique identifier for this segment
ID of the source content (from Content.id). The backend looks up the source file, type, game, title, and IGDB ID from application state.
Start timestamp in seconds
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:
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:
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:
IDs of the videos to check (from Content.id).
Example:
sendMessageToBackend('CheckMerge', {
Ids: ["9f1c8d0a-...", "4b7e2f13-..."]
});Creates AI-generated highlights from a video.
Parameters:
ID of the source content to process (from Content.id).
Example:
sendMessageToBackend('CreateAiClip', {
Id: "9f1c8d0a-..."
});Content Management
Deletes a single piece of content.
Parameters:
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:
Array of content IDs to delete (from Content.id).
Example:
sendMessageToBackend('DeleteMultipleContent', {
Ids: [
"9f1c8d0a-...",
"b2ac7e11-..."
]
});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:
ID of the content to upload (from Content.id).
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:
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:
ID of the content to compress (from Content.id).
Example:
sendMessageToBackend('CompressVideo', {
Id: "9f1c8d0a-..."
});Bookmarks
Adds a bookmark to a piece of content.
Parameters:
ID of the content to bookmark (from Content.id).
Client-generated bookmark ID.
Bookmark type (parsed as BookmarkType).
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"
});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:
Preset name: low, standard, high, or custom
Example:
sendMessageToBackend('ApplyVideoPreset', {
preset: "high"
});Applies a clip quality preset (see PresetsService.ApplyClipPreset).
Parameters:
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:
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:
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:
Full path to the source video file.
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:
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:
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:
Settingswith persisted settingsStatewith runtime application stateGameListwith available gamesAppVersionwith backend version (only when an update manager build is available)UpdateProgressif an update download/install was in flight when the client (re)connected, replaying the most recent progress payloadReleaseNotesasynchronously, 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.
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):
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 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.
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)
Available video codecs/encoders
OBS versions known to the updater
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:
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:
Clip operation ID
Progress percentage (0-100), or -1 for error
Array of segments being processed (mirrors what was sent in CreateClip)
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:
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.
File being uploaded
Title submitted with the upload (mirrored in every progress event)
Progress percentage (0-100), or -1 for error
Upload status: "uploading", "processing", "done", or "error"
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:
Import operation ID
Set to "recover" when the progress is for a file recovery. Omitted for imports.
Current file being imported
Progress percentage for current file
Import status: "importing", "done", or "error"
Total number of files to import
Index of current file (0-based)
Status message
Reports progress of video compression.
Content:
File being compressed
Progress percentage (0-100), or -1 for error
Status: "compressing", "done", "skipped", or "error"
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:
Source file being compressed for the clipboard copy
Progress percentage (0-100)
Status: "compressing", "done", or "error"
Status or error 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:
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:
Whether migration is currently running
Description of current migration
Reports progress of a content-folder migration triggered by MigrateContent.
Content:
Migration operation ID
File currently being moved
Progress percentage (0-100), or -1 for error
Migration status (e.g. "migrating", "done", "error")
Total number of files in the migration
Index of the file currently being moved
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:
Modal title
Optional subtitle
Modal body text
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:
Unique ID for this warning
Warning title
Warning description
Text for confirm button
Text for cancel button
Action to perform if confirmed
Data needed to perform the action
Prompts user to recover files after a crash.
Content:
Array of recoverable file objects
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:
Game name (derived from executable name)
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:
Array of release note objects
Version number
Release notes in base64-encoded markdown
ISO 8601 date string
Triggers the frontend to display release notes.
Content: Version string to show notes for
Heartbeat
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:
- Logs the error with Serilog
- Sends a progress event with
progress: -1and anerrorfield - Optionally shows a
ShowModalmessage 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
Import message type definitions from Models/WebSocketMessages.ts for type safety.
Progress events can be: in-progress (0-100), complete (100), or error (-1). Handle all cases.
Use window.addEventListener('websocket-message') to react to backend events globally.
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>
);
}