Event Reference
All WebSocket messages are JSON objects with a type field. The transport is PartySocket (native WebSocket under the hood).
Client β Serverβ
create_sessionβ
Sent by host to create a new session.
{
type: 'create_session';
title?: string; // optional; falls back to first wine name
hostId: string; // e.g. 'TANNIC-FALCON'
timerSeconds: number; // 15β120
wines: Array<{
name: string;
questions: Array<{
category: 'color' | 'region' | 'grape_variety' | 'vintage_year' | 'wine_name';
correctAnswer: string;
distractors: [string, string, string];
}>;
}>;
}
update_sessionβ
Sent by host to update wines while the session is in the waiting phase (before game starts). Rejected with an error if phase !== 'waiting'.
{
type: 'update_session';
title?: string; // optional; falls back to first wine name
hostId: string;
timerSeconds: number;
wines: Array<{
name: string;
questions: Array<{
category: 'color' | 'region' | 'grape_variety' | 'vintage_year' | 'wine_name';
correctAnswer: string;
distractors: [string, string, string];
}>;
}>;
}
join_sessionβ
Sent by participant to join a waiting session.
{ type: 'join_session'; code: string }
rejoin_hostβ
Sent by host on socket open to re-authenticate (after page refresh or reconnect).
{ type: 'rejoin_host'; hostId: string }
rejoin_sessionβ
Sent by participant on socket open when a rejoin credential (id, code) is in localStorage.
{ type: 'rejoin_session'; pseudonym: string }
Cross-device rejoin: open
https://sommelier-arena.ducatillon.net/play?code=X&id=YOUR-PSEUDONYMon the new device. The?id=param restores the credential before the socket opens.
host:start / host:pause / host:resume / host:reveal / host:next / host:endβ
Host control events β no additional payload.
{ type: 'host:start' }
{ type: 'host:pause' }
{ type: 'host:resume' }
{ type: 'host:reveal' }
{ type: 'host:next' }
{ type: 'host:end' }
submit_answerβ
Sent by participant to select or change an answer.
{ type: 'submit_answer'; questionId: string; optionId: string }
Server β Clientβ
session:createdβ
Sent to host after successful create_session.
{ type: 'session:created'; code: string; hostId: string }
host:session_updatedβ
Sent to host after a successful update_session.
{
type: 'host:session_updated';
wines: Wine[]; // updated wines list
timerSeconds: number;
sessionTitle: string;
}
participant:joinedβ
Sent to the joining participant.
{ type: 'participant:joined'; pseudonym: string }
lobby:updatedβ
Broadcast to all connected clients when a participant joins or leaves the lobby.
{ type: 'lobby:updated'; participants: string[] }
host:state_snapshotβ
Sent to host on rejoin_host (full current state).
{
type: 'host:state_snapshot';
code: string;
hostId: string;
phase: SessionPhase;
participants: string[];
question: QuestionPayload | null;
rankings: RankingEntry[];
}
participant:state_snapshotβ
Sent to participant on rejoin_session.
{
type: 'participant:state_snapshot';
pseudonym: string;
phase: SessionPhase;
question: QuestionPayload | null;
}
sessions:listβ
β οΈ Not currently active β requires
HOSTS_KVbinding inpartykit.json, which is disabled. Session history is served fromlocalStorageon the client. See Data Persistence for details.
Sent to host listing their past and active sessions (sourced from KV when enabled).
{
type: 'sessions:list';
sessions: Array<{
code: string;
title: string;
createdAt: string;
status: 'waiting' | 'active' | 'ended';
participantCount: number;
finalRankings?: RankingEntry[];
}>;
}
game:questionβ
Broadcast when a new question starts.
{
type: 'game:question';
questionId: string;
prompt: string;
category: QuestionCategory;
options: Array<{ id: string; text: string }>; // shuffled; no 'correct' flag
roundIndex: number;
questionIndex: number;
totalQuestions: number;
totalRounds: number;
timerMs: number;
}
game:timer_tickβ
Emitted every second during question_open and question_paused.
{ type: 'game:timer_tick'; remainingMs: number }
game:timer_paused / game:timer_resumedβ
{ type: 'game:timer_paused'; remainingMs: number }
{ type: 'game:timer_resumed'; remainingMs: number }
game:participant_answered (host only)β
{ type: 'game:participant_answered'; answeredCount: number; totalCount: number }
game:answer_revealedβ
Sent to host:
{
type: 'game:answer_revealed';
correctOptionId: string;
results: Array<{ pseudonym: string; points: number; totalScore: number }>;
}
Sent to each participant:
{
type: 'game:answer_revealed';
correctOptionId: string;
myPoints: number;
myTotalScore: number;
}
game:question_leaderboardβ
Broadcast after each question's answer is revealed, showing current standings.
{
type: 'game:question_leaderboard';
rankings: RankingEntry[];
questionIndex: number;
totalQuestions: number;
roundIndex: number;
totalRounds: number;
}
game:round_leaderboardβ
{
type: 'game:round_leaderboard';
rankings: RankingEntry[];
roundIndex: number;
totalRounds: number;
}
game:final_leaderboardβ
{ type: 'game:final_leaderboard'; rankings: RankingEntry[] }
session:endedβ
{ type: 'session:ended' }
errorβ
{ type: 'error'; message: string; code: string }
code | When |
|---|---|
SESSION_NOT_FOUND | join_session sent to a room with no wines (session doesn't exist) |
GAME_STARTED | join_session sent after the game has already begun; or update_session sent after game has started |
SESSION_FULL | join_session when 10 participants already connected |
INVALID_PSEUDONYM | rejoin_session with an unknown pseudonym |
INVALID_HOST_ID | rejoin_host with a mismatched host ID |
SESSION_EXISTS | create_session when a session already exists in this room |
NO_WINES | create_session or update_session sent with an empty wines array |
Connection / Lifecycle Eventsβ
server:state_snapshotβ
Sent by the backend to every client immediately after they connect or reconnect. Contains the current session phase so clients can restore their UI state.
Direction: Server β Client
Payload:
{
"phase": "waiting | question_open | question_paused | question_revealed | question_leaderboard | round_leaderboard | ended",
"code": "1234"
}
Participants use this event to detect when a session has ended while they were disconnected. If
phase === "ended", the client transitions to the ended state and clears the rejoin credential from localStorage.
sessions:listβ
β οΈ Not currently active β requires
HOSTS_KVbinding, which is disabled. Session history comes fromlocalStorageonly. See Data Persistence.
Sent to the host in response to connecting with a host ID. Contains all sessions stored under that host ID (when HOSTS_KV binding is enabled).
Direction: Server β Host
Payload:
[
{
"code": "1234",
"title": "My Wine Night",
"createdAt": "2024-01-01T00:00:00.000Z",
"status": "waiting | ended",
"participantCount": 3
}
]
host:state_snapshotβ
Sent to a host on reconnect to restore their session state.
Direction: Server β Host
Payload: Same shape as the full host game state (participants, current question, phase, etc.)
participant:state_snapshotβ
Sent to a participant on reconnect to restore their game state.
Direction: Server β Participant
Payload: Same shape as the full participant game state (phase, current question, selected option, etc.)
Typesβ
type QuestionCategory = 'color' | 'region' | 'grape_variety' | 'vintage_year' | 'wine_name';
type SessionPhase = 'waiting' | 'question_open' | 'question_paused' | 'question_revealed' | 'question_leaderboard' | 'round_leaderboard' | 'ended';
interface RankingEntry {
pseudonym: string;
score: number;
rank: number;
}