Skip to main content

StreamElements Tokens

Overview

The SE Tokens module manages StreamElements JWT tokens used for receiving tip/donation events via the StreamElements WebSocket API. Tokens are stored encrypted at rest and scoped per account+platform. Creating a token automatically starts a StreamElements WebSocket worker; deleting the last token for an account stops it.

Supported platforms: Twitch, YouTube, Kick, Trovo.

Architecture

Dashboard UI
|
v
Next.js API Proxy (/api/se-tokens)
|
+---> REST API (GET/POST/DELETE /v1/se-tokens)
| |
| +--> WorkerManager (start/stop SE WebSocket worker)
|
+---> GraphQL (SeTokenQuery / SeTokenMutation)
|
v
db::se_tokens (PostgreSQL, encrypted token storage)

Token Lifecycle

  1. Create -- User provides their StreamElements JWT token and platform. The token is encrypted with AES using a derived key from auth.token_encryption_key and stored via upsert (one token per account+platform).
  2. Worker Start -- The REST layer starts a StreamElementsConfig worker via WorkerManager, passing the unencrypted token for the WebSocket connection.
  3. List -- Returns tokens with masked hints (first 4 + last 4 chars, middle replaced with ***). The actual token value is never returned.
  4. Delete -- Removes the token. If no tokens remain for the account, the SE worker is stopped via WorkerManager.stop_platform_worker.
  5. Boot Recovery -- list_all_accounts_with_tokens is used at server startup to auto-start SE workers for all accounts with stored tokens.

Token Masking

Tokens returned to clients show only a hint: eyJh***ab12. If the token is 8 characters or shorter, or decryption fails, **** is returned instead.

API

REST Endpoints

MethodPathDescriptionPermission
GET/v1/se-tokensList all SE tokens (masked)se-tokens:read
POST/v1/se-tokensCreate/update an SE tokense-tokens:create
DELETE/v1/se-tokens/{id}Delete an SE tokense-tokens:delete

POST /v1/se-tokens

{
"platform": "twitch",
"token": "eyJhbGciOiJI...",
"label": "My SE Token"
}

Validation:

  • platform must be one of: twitch, youtube, kick, trovo
  • token must not be empty

Response (201): Returns the created token with masked hint and starts the SE worker.

GraphQL Queries

QueryArgsReturnsPermission
seTokens--[SeToken]se-tokens:read

GraphQL Mutations

MutationArgsReturnsPermission
createSeTokenplatform: String, token: String, label?: StringSeTokense-tokens:create
deleteSeTokenid: UUIDBooleanse-tokens:delete

Note: The GraphQL layer does not have access to WorkerManager, so worker lifecycle is handled exclusively by the REST endpoints.

GraphQL Types

type SeToken {
id: UUID!
platform: String!
tokenHint: String!
label: String
createdAt: String!
updatedAt: String!
}

Permissions

PermissionDescription
se-tokens:readList SE tokens
se-tokens:createCreate/update SE tokens
se-tokens:deleteDelete SE tokens

Included in: Owner, Administrator roles.

Database

Table: se_tokens

ColumnTypeDescription
idUUID (PK)Token ID
account_idUUID (FK)Owning account
platformTEXTPlatform identifier
tokenTEXTEncrypted JWT token
labelTEXTOptional user label
created_atTIMESTAMPTZCreation timestamp
updated_atTIMESTAMPTZLast update timestamp

Unique constraint: (account_id, platform) -- one token per platform per account.

DB Functions

FunctionDescription
list_tokensList all tokens for an account, ordered by platform
upsert_tokenInsert or update on (account_id, platform) conflict
delete_tokenDelete by ID with account ownership check
has_tokensCheck if an account has any tokens
list_all_accounts_with_tokensBoot-time recovery: all accounts grouped with their tokens

Key Files

FilePurpose
apps/api/src/graphql/se_tokens.rsGraphQL queries and mutations
apps/api/src/routes/se_tokens.rsREST endpoints with worker lifecycle management
apps/api/src/db/se_tokens.rsDatabase CRUD operations
crates/lo-auth/src/rbac.rsPermission constants (se-tokens:read/create/delete)