# Validate typed WebSocket handlers

Build a `/match` service with independent join, move, and resume handlers. Each handler owns the schema for its inbound message. The route derives the available message types from those handlers; no second message map or `message.action` dispatcher is needed.

## Explain it like I'm five

The URL is the service's address. The message `type` tells Redweb which handler should receive it. That handler has its own form describing the payload it accepts, so Redweb checks the message before the handler runs.

## Follow the design

1. [The message schemas](../../recipes/socket/schema.ts) define the client commands together in `SCHEMAS`; object keys become their message names.
2. [The route](../../recipes/socket/app.tsx) selects `/match`, declares protocol version negotiation explicitly, and registers the schema-owning `Join`, `Move`, and `Resume` handlers.
3. Each handler receives the validated payload as its second argument and the original envelope metadata as an optional third argument. `Join` creates a server-owned player session, `Move` changes its coordinates, and `Resume` reclaims it using a private bearer token.
4. Server handlers create and send their own response payloads. Unit and integration tests verify those responses; Redweb does not require a duplicate server-to-client schema map.

Connect to `ws://localhost:8181/match?redwebVersion=1` during local development. Use `redweb/client`'s `ProtocolClient` if you want a helper for protocol envelopes. It wraps an existing WebSocket; it does not open or reconnect one. Use WSS outside local development. The [schema-validated handler reference](../SOCKET_HANDLERS.md) explains validation and failure behavior.

## Check that it works

Join with two independent clients, move one, then disconnect and resume it with its session token. Send invalid coordinates and a malformed raw message to verify server-side input validation. The [real-socket acceptance test](../../recipes/socket/app.test.cjs) covers those sequences.

## This is not a complete game backend

The example bounds coordinate values; it does not prove a move obeys your game's speed, turn, or collision rules. Add authoritative game rules and authentication. Keep session tokens private: possession permits resume and connection takeover. Sessions are capped at 100, expire 30 seconds after disconnect, and are lost on restart. They are not a durable or cross-worker identity store.

Per-connection ordering is not exactly-once delivery. After a disconnect, a client may not know whether its last action completed; reconcile state before retrying side effects. Choose transport limits from measured load and read [operations](../MULTIPLAYER_OPERATIONS.md) and [runtime retry boundaries](../RUNTIME_DIAGNOSTICS.md).
