Longer, worked examples that combine the install, the transport swap, lane selection, and the lifecycle callbacks into something you can run. For the per-method reference see SDK methods; for short snippets see the Cookbook.

Recipe 1 — Port the ECS racing sample onto ClutchCall

Unity’s ECS-Network-Racing-Sample is a DOTS / Netcode for Entities project. Swapping its transport is the fastest way to confirm an end-to-end match runs over the ClutchCall relay mesh.
1

Add the package

Open the sample in Unity and add the transport via Package Manager → Add package from git URL:
https://github.com/clutchcall/core-sdk.git?path=unity/com.clutchcall.transport
2

Swap the transport on the NetworkManager

Find the prefab holding the NetworkManager. Remove UnityTransport, add ClutchCallTransport, and repoint Netcode at it:
var nm = manager.GetComponent<NetworkManager>();
nm.NetworkConfig.NetworkTransport = nm.GetComponent<ClutchCallTransport>();
3

Configure relay + token

Drive the inspector fields from a small bootstrap so host and clients agree on the room:
var t = nm.GetComponent<ClutchCallTransport>();
t.RelayHost = "relay.clutchcall.dev";
t.RoomId    = "race-01";
t.Token     = await FetchRoomTokenAsync("race-01", localPlayerId);
4

Assign host vs client roles

Keep the sample’s own “Go in game” flow. The host opens the room’s authority namespace; each client announces a discovery namespace and joins it:
if (isHost) nm.StartHost();
else        nm.StartClient();
5

Build players (not the editor)

Editor batch mode does not drive Netcode for Entities. Build a player for each role and launch them. The [Server] / [Client] connection lifecycle completes, NetworkId is assigned, and the cars sync.
What flows where:
  • Car transforms / physics ticks ride the unreliable lane (QUIC datagrams) — high frequency, latest-wins, no head-of-line blocking.
  • Spawns, despawns, and race-start / finish RPCs ride the reliable lane (MoQT subgroup streams) — ordered, guaranteed.
Over a full match, tens of thousands of datagrams flow through the relay. You can sanity-check link health from the transport:
foreach (var id in nm.ConnectedClientsIds)
    Debug.Log($"peer {id}: rtt={nm.GetComponent<ClutchCallTransport>().GetCurrentRtt(id):F0}ms");
If a client fails to appear in the host roster, confirm both built players share the same RoomId and a valid token. Roster membership is driven by the discovery namespace announcement — a mismatched room means the host never sees the announce.

Recipe 2 — Headless dedicated server with matchmaking

A common production shape: a dedicated server player runs the authority for a room, and clients fetch a room-scoped token from your control-plane API before connecting. The server has no display and starts on launch.

Server bootstrap

using Unity.Netcode;
using ClutchCall.Transport;
using UnityEngine;

public class DedicatedServer : MonoBehaviour {
    [SerializeField] NetworkManager nm;

    async void Start() {
        if (!Application.isBatchMode) return; // server build only

        var roomId = Args.Get("room", "lobby-1");
        var t = nm.GetComponent<ClutchCallTransport>();
        t.RelayHost = "relay.clutchcall.dev";
        t.RoomId    = roomId;
        t.Token     = await MintServerTokenAsync(roomId);

        nm.OnClientConnectedCallback  += id => Debug.Log($"join  {id} ({nm.ConnectedClientsIds.Count} in room)");
        nm.OnClientDisconnectCallback += id => Debug.Log($"leave {id}");
        nm.OnTransportFailure         += () => { Debug.LogError("relay session failed"); Application.Quit(1); };

        nm.StartServer();
        Debug.Log($"authority up for room {roomId}");
    }
}

Client matchmaking + join

The client asks the control-plane API for a room and a token, configures the transport, then connects:
public class Matchmaker : MonoBehaviour {
    [SerializeField] NetworkManager nm;

    public async Task JoinAsync(string playerId) {
        // 1. control-plane API hands back a room + short-lived token
        var match = await Api.PostAsync<MatchResp>(
            "https://api.clutchcall.dev/matchmake",
            new { player = playerId });

        // 2. point the transport at the assigned room
        var t = nm.GetComponent<ClutchCallTransport>();
        t.RelayHost = match.RelayHost;     // nearest POP
        t.RoomId    = match.RoomId;
        t.Token     = match.Token;         // room-scoped, short-lived

        // 3. join — datagram lane carries inputs, reliable carries spawns
        nm.OnTransportFailure += () => SceneManager.LoadScene("Lobby");
        nm.StartClient();
    }
}

Why this shape works

Both the dedicated server and the clients connect outbound to the nearest relay POP. There is no inbound port to open and no relay infrastructure to run — the mesh fans state and input between regions.
The server holds a single QUIC connection for the authority; each client holds one for its session. Reliable and unreliable lanes multiplex over it — no second socket, no separate reliability layer to tune.
The control-plane API mints a token bound to a room and a short TTL, so a leaked client token cannot join arbitrary matches. The server mints its own authority token the same way.

Recipe 3 — Lane discipline for a fast-paced shooter

When you control the gameplay code, route traffic onto the right lane explicitly. The rule of thumb: latest-wins high-frequency traffic is unreliable; anything you cannot drop is reliable.
public class PlayerSync : NetworkBehaviour {
    // 60 Hz transform updates — drop-safe, latest-wins → unreliable datagram
    [Rpc(SendTo.Server, Delivery = RpcDelivery.Unreliable)]
    void MoveRpc(Vector3 pos, Quaternion rot, ulong tick) =>
        server.IntegrateMove(OwnerClientId, pos, rot, tick);

    // hit registration — must arrive, must be ordered → reliable subgroup stream
    [Rpc(SendTo.Server, Delivery = RpcDelivery.Reliable)]
    void FireRpc(ulong targetTick, Vector3 origin, Vector3 dir) =>
        server.ResolveShot(OwnerClientId, targetTick, origin, dir);

    void Update() {
        if (!IsOwner) return;
        // guard the datagram budget for the move payload
        if (transform.HasMovedSince(lastTick))
            MoveRpc(transform.position, transform.rotation, NetworkTickSystem.Tick);
    }
}
Keep unreliable payloads under MaxPayloadSize(). A QUIC datagram larger than the negotiated max is rejected, not fragmented — so an oversized snapshot silently fails to send rather than splitting across packets. For large authoritative state, send a reliable baseline and unreliable deltas.

Details

Wire model, lanes, and the architecture.

Games modality

The pure-MoQT face of the same wire model, for non-Unity engines.