> ## Documentation Index
> Fetch the complete documentation index at: https://www.floe.one/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# How Floe Signaling Works

> How the Floe signaling server brokers the initial WebRTC handshake between sender and receiver without ever seeing the files being transferred.

When you create a transfer in Floe, a unique room is created on the signaling server. The sender shares a code or link. When the recipient joins, the two devices introduce themselves through the server and exchange the information they need to connect directly. Once that handshake is complete, the server steps aside.

The signaling server never touches file data, never stores your files or any record of what you send, and plays no part once the WebRTC connection is established. The only thing it keeps is a single anonymous running total of bytes transferred, used for the public counter on the homepage (see below).

## Connection lifecycle

1. You create a transfer and share the link or short code with the recipient.
2. Both devices join the same room on the signaling server.
3. The server assigns roles: the first peer to join is the **sender**, the second is the **receiver**.
4. Peers exchange a WebRTC offer, answer, and ICE candidates through the server.
5. Once a usable network path is found, a direct data channel opens between the two devices.
6. The signaling server plays no further part. All file data flows over the data channel.

## What the signaling server does

* Assigns roles: the first peer to join a room becomes the **sender**, the second becomes the **receiver**.
* Relays WebRTC negotiation messages: the initial offer, the answer, and ICE candidates.
* Issues short-lived TURN credentials when a TURN server is configured.
* Registers short human-readable codes (e.g. `olive-tiger-castle`) that map to room IDs.
* Adds the size of each completed transfer to one anonymous global byte total, for the public homepage counter.

## What the signaling server never does

* Handle file data of any kind.
* Store files, file names, transfer content, or logs of what you send or receive.
* Link the byte total to you. The counter is a single shared number with no per-user or per-transfer records.
* Persist room or peer information after the session ends.

<Accordion title="Technical details">
  **Transports:** Browser clients connect via Socket.IO. CLI clients connect via WebSocket at `/ws`. Both share a single in-memory `rooms` registry (`Map<roomId, [peer, peer]>`), so browser-to-CLI transfers are fully supported.

  **WebRTC negotiation flow:**

  1. Sender joins the room (`join-room` event) and receives the `sender` role.
  2. When the receiver joins, the server emits `user-connected` to the sender.
  3. Sender creates an SDP offer and sends it via the `signal` event.
  4. Receiver sets the remote description, creates an SDP answer, and sends it back.
  5. Both peers exchange ICE candidates via `signal` events (trickle ICE).
  6. Once ICE negotiation completes, the WebRTC data channel opens directly between the peers.

  **Short codes:** Registered via `POST /api/code` with a room UUID. The server picks three random words from a 288-word list using a cryptographically secure random source (`crypto.randomInt`, not `Math.random`), because the code phrase is the only secret guarding a transfer, and maps them to the room ID with a 10-minute TTL. `GET /api/code/:code` resolves a code back to its room UUID.

  **TURN credentials:** Issued via `GET /api/turn-credentials`. On floe.one the server mints short-lived credentials (up to 24 hours) from Cloudflare's Realtime TURN service. Self-hosted instances can instead use coturn with HMAC-SHA1 credentials via `TURN_SECRET`, or skip TURN entirely, in which case the endpoint returns public STUN servers only.

  **Rate limiting:** 30 connections per IP per 60 seconds for Socket.IO and WebSocket; 20 requests per IP per 60 seconds for the TURN credential endpoint; 60 requests per IP per 60 seconds shared across `POST /api/code` and `GET /api/code/:code`; 60 reports per IP per 60 seconds for the stats endpoint. The signaling server also caps the number of simultaneously-live room codes (10000 by default); `POST /api/code` returns `503` when that cap is hit.

  **Global stats counter:** After a transfer completes, the receiver sends `POST /api/stats/report` with just the byte count. The sender never reports. The server keeps a single anonymous running total (cached in memory, optionally persisted to Upstash Redis) and serves it from `GET /api/stats` for the homepage counter. No file names, contents, or per-transfer records are stored. The total is viewable only in the browser; the CLI receiver contributes but never displays it. Both the browser receiver and CLI receiver can opt out (browser: "Contribute to global stats" toggle; CLI: `--no-report` flag or `FLOE_NO_STATS=1`). See the [architecture reference](/docs/reference/architecture#global-statistics-counter) for details.

  **Data-channel transfer protocol:** Once the WebRTC data channel opens, the sender runs this sequence for each file:

  1. Sends a JSON `metadata` message with the filename, size in bytes, and the file's index and total count in the batch.
  2. Waits for the receiver to reply with a JSON `ack` message. The ack carries a byte offset, so a transfer can resume mid-file.
  3. Streams the file as binary chunks until the whole file is sent. Both senders size chunks adaptively to what the connection negotiates, up to 256 KB, and apply backpressure-based flow control, pausing when the data channel's send buffer fills.
  4. Sends a JSON `end` message to signal completion.

  For multi-file transfers, this four-step sequence repeats for each file in order.
</Accordion>
