Skip to content

Messages

The PairKit wire protocol is a discriminated union of JSON messages with a stable type field. Current protocol version: 1.

The TypeScript schema in messages.ts is the source of truth. C# types under sdks/unity/Runtime/Protocol/ are generated from it; never hand-edited.

Client → Server

Originated by either a host (Unity) or a controller (phone), bound for the relay.

TypeDescription
session.createHost → relay: open a new session and receive a short code. Optionally carries a flow for multi-screen experiences, or resume_code + resume_host_id to reattach to an existing session after a relay restart.
session.joinController → relay: join an existing session by code. Optionally carries resume_player_id for transparent reconnect within the grace window.
session.leaveEither side → relay: clean disconnect. Optional reason field.
control.send_to_playerHost → relay: deliver a custom event to one player. The relay forwards it as control.event.
control.broadcastHost → relay: deliver a custom event to every player in the session. ACK-tracked variants exist for delivery confirmation.
control.ackController → host: ACK that a control.broadcast was received. Drives the BroadcastResult returned by BroadcastToAllPlayersAsync.
screen.queryController → relay: ask for the current screen state. Fallback when the controller’s local view diverged after a reconnect race.

Server → Client

Originated by the relay, bound for either side.

TypeDescription
session.createdRelay → host: session was allocated. Carries the 4-character code, the secret host_id (treat as a bearer token), and relay_capabilities (e.g. strict_payloads).
session.joinedRelay → controller: join succeeded. Carries the assigned player_id, the layout/flow to render, and any current screen state for resume.
player.joinRelay → host: a controller joined the session.
player.leaveRelay → host: a controller left. reasonuser_left
player.updateRelay → host: a player’s display name or color changed mid-session.
control.eventRelay → controller: a host-originated control event (e.g. vibrate, flash). Routed to session-level handlers or per-widget onControl callbacks.
control.broadcast_resultRelay → host: aggregated delivery result (Delivered / Missed / TimedOut) for an ACK-tracked control.broadcast.
screen.stateRelay → controller: response to screen.query.
system.errorRelay → either side: error report with a stable code (see Errors) and a human-readable message.

Either direction

These messages flow in both directions (e.g. the relay forwards a host’s broadcast to controllers using the same envelope).

TypeDescription
input.widget_eventController → host (forwarded by relay): a widget emitted an event. Carries widget_id, event_type, and an arbitrary payload shaped per widget.
input.responseController → host: response to a control.request (PromptAsync / RequestAsync). Carries the request id and the response payload.
control.requestHost → relay → controller: ask a single player for one response (powers PromptAsync / RequestAsync).
control.request_cancelEither side: cancel an outstanding control.request.
control.resend_requestHost → controller: ask the phone to replay missing stroke_progress events for a drawing_canvas (powers RequestResendAsync).
control.resend_failedController → host: phone cannot replay (buffer expired, widget not ready, …). Drives the Rejected outcome of RequestResendAsync.
control.resend_acceptedController → host: phone is replaying. Carries replayed_count. Drives the Accepted outcome of RequestResendAsync.
screen.showHost → relay → controllers: move targeted players to a named screen. revision is monotonic per-(session, player) globally.
screen.update_dataHost → relay → controllers: shallow-merge a patch into a player’s current screen data. Used to update bound values like {score} without changing the screen.
screen.acknowledgeController → host: ACK that a screen transition was applied locally. Carries the screen name and revision so the host can detect dropped transitions.
system.pingEither side: keepalive + clock-sync. Sender stamps sent_at (unix ms); receiver echoes it in system.pong.
system.pongEither side: response to system.ping. The SDK uses (host_recv − pong_send) to estimate clock skew for timer widgets.

Adding a new message

  1. Add the interface to messages.ts and a new entry in MESSAGE_TYPES.
  2. Add it to the appropriate union (ClientToServerMessage / ServerToClientMessage).
  3. Run pnpm --filter @pairkit/shared-protocol generate:csharp to regenerate Unity types.
  4. Add a description to MESSAGE_DESCRIPTIONS in scripts/generate-docs.ts and run pnpm --filter @pairkit/shared-protocol generate:docs.

CI fails any PR that adds a message without all three steps.