WebSocket Protocol
Low-level WebSocket message format for direct integration.
Connection
wss://watchtower-api.watchtower-host.workers.dev/v1/rooms/{code}/ws?playerId={id}&apiKey={key}Query parameters:
playerId— Your unique player identifierapiKey— Your API key (required for auth)
Message Format
All messages are JSON. Every message has a type field.
Client → Server
Update Player State
{
"type": "player_state",
"state": {
"x": 100,
"y": 200,
"sprite": "running"
}
}Update Game State (Host Only)
{
"type": "game_state",
"state": {
"phase": "playing",
"round": 1,
"scores": {}
}
}Broadcast Message
{
"type": "broadcast",
"data": { "type": "chat", "message": "Hello!" },
"excludeSelf": true
}Send to Specific Player
{
"type": "send",
"to": "player-id",
"data": { "type": "private", "text": "Hey" }
}Transfer Host (Host Only)
{
"type": "transfer_host",
"newHostId": "player-id"
}Ping
{
"type": "ping"
}Server → Client
Connected
Sent immediately after WebSocket connection established.
{
"type": "connected",
"playerId": "your-player-id",
"room": {
"code": "ABCD",
"gameId": "my-game",
"hostId": "host-player-id",
"players": [
{ "id": "player-1", "joinedAt": 1706400000000 },
{ "id": "player-2", "joinedAt": 1706400100000 }
],
"playerCount": 2
},
"playerStates": {
"player-1": { "x": 100, "y": 200 },
"player-2": { "x": 300, "y": 200 }
},
"gameState": {
"phase": "lobby"
}
}Player Joined
{
"type": "player_joined",
"playerId": "new-player-id",
"playerCount": 3
}Player Left
{
"type": "player_left",
"playerId": "leaving-player-id",
"playerCount": 2
}Players Sync (Batch)
Sent at 20Hz with all player states.
{
"type": "players_sync",
"players": {
"player-1": { "x": 105, "y": 200 },
"player-2": { "x": 295, "y": 210 }
}
}Player State Update (Individual)
Sent immediately when a player updates (for lower latency).
{
"type": "player_state_update",
"playerId": "player-1",
"state": { "x": 105, "y": 200 }
}Game State Sync
{
"type": "game_state_sync",
"state": {
"phase": "playing",
"round": 1
}
}Host Changed
{
"type": "host_changed",
"hostId": "new-host-player-id"
}Message (Broadcast/Direct)
{
"type": "message",
"from": "sender-player-id",
"data": { "type": "chat", "message": "Hello!" }
}Pong
{
"type": "pong",
"timestamp": 1706400000000
}Example: JavaScript Client
const ws = new WebSocket(
'wss://watchtower-api.watchtower-host.workers.dev/v1/rooms/ABCD/ws' +
'?playerId=my-player&apiKey=wt_live_xxx'
)
ws.onopen = () => {
console.log('Connected!')
}
ws.onmessage = (event) => {
const data = JSON.parse(event.data)
switch (data.type) {
case 'connected':
console.log('Room:', data.room)
break
case 'players_sync':
updatePlayers(data.players)
break
case 'message':
handleMessage(data.from, data.data)
break
}
}
// Send player state
function sendState(state) {
ws.send(JSON.stringify({
type: 'player_state',
state: state
}))
}
// Broadcast message
function broadcast(data) {
ws.send(JSON.stringify({
type: 'broadcast',
data: data,
excludeSelf: true
}))
}Update Frequency
| Message Type | Frequency |
|---|---|
players_sync | 20Hz (every 50ms) |
player_state_update | Immediate |
game_state_sync | On change |
message | Immediate |
Keep-Alive
Send periodic ping messages to keep the connection alive. The server responds with pong.
// Send ping every 30 seconds
setInterval(() => {
ws.send(JSON.stringify({ type: 'ping' }))
}, 30000)