How Browser-to-Browser File Transfer Actually Works
When you send a file with a cloud service, the file makes two trips: up to a data center, then down to the recipient. Browser-to-browser transfer removes the middle stop. Two browsers negotiate a direct, encrypted channel and the bytes flow between them. The technology that makes this possible in every modern browser is WebRTC, specifically a part of it called the data channel. This article walks the whole path, from the moment you tap Send to the moment the receiver's checksum matches yours, with the real protocol details rather than a diagram of two laptops and an arrow.
The cast
Four parties take part in every transfer, and only two of them ever see the file:
- The sending browser: reads the file from disk, encrypts, sends.
- The receiving browser: decrypts, verifies, writes to disk.
- The signaling server: introduces the two browsers to each other. It carries a few kilobytes of negotiation messages and then falls silent.
- STUN and TURN servers: a mirror that tells each browser its public address, and a relay that forwards encrypted packets when no direct route exists.

Step 1: Introduction, the room and the signaling channel
Two browsers that have never met need to exchange connection details, and they cannot talk to each other yet. So the sender first creates a room on the signaling server (in ShareDrop.org, tapping Send and picking files does this) and receives a short code and a QR code that encode the room identifier plus a single-use invite credential. The receiver scans or types the code, and the signaling server now has two WebSocket connections that belong to the same room.
From here on, signaling is a postbox: each browser drops in a message addressed to the room and the server hands it to the other side. What travels through it is small and specific: session descriptions (the “offer” and “answer” described next) and ICE candidates (network addresses). What never travels through it is the file, its name, its size, or its checksum; those exist only inside the encrypted channel that comes later. ShareDrop.org's signaling keeps the room identifier and connection state in memory with a time-to-live measured in minutes, and forgets the room when the session ends (what the server does and does not see).
One design decision here matters more than it looks: the receiver must explicitly accept the incoming connection before negotiation proceeds. A room code that leaked could otherwise let a stranger connect silently. The prompt you see (“A device wants to connect”) is that gate.
What a signaling message looks like
For the curious, here is the shape of the traffic on the signaling channel, simplified but faithful. The receiver joins:
{ "type": "join", "room": "SYNN94", "invite": "single-use-token" }The sender is told someone wants in and, after the user taps Accept, sends its offer:
{ "type": "offer", "sdp": "v=0
o=- 4611731400430051336 2 IN IP4 127.0.0.1
…a=fingerprint:sha-256 7B:8B:…
…" }And as each side discovers a network address, it trickles it across:
{ "type": "candidate", "candidate": "candidate:2 1 udp 1686052607 203.0.113.7 41000 typ srflx raddr 192.168.1.23 rport 51234" }That is the entire vocabulary: join, offer, answer, candidate, and a few housekeeping messages. A complete negotiation is a few kilobytes. Compare that to the file, which never appears here at all: there is no message type for it.
Step 2: The offer, and what a session description actually contains
The sending browser now generates an SDP offer: a text document describing the connection it proposes. Here is the part of a real Chrome offer that matters for a data channel, lightly trimmed:
m=application 9 UDP/DTLS/SCTP webrtc-datachannel
c=IN IP4 0.0.0.0
a=ice-ufrag:4ZcD
a=ice-pwd:2/1muCWoOi3uLifh0NuRHlSEQx
a=ice-options:trickle
a=fingerprint:sha-256 7B:8B:F0:65:5F:78:E2:51:3B:AC:6F:F3:3F:46:1B:35:DC:B8:5F:64:1A:24:C2:43:F0:A1:58:D0:A1:2C:19:08
a=setup:actpass
a=mid:0
a=sctp-port:5000
a=max-message-size:262144m=application … webrtc-datachannel: this session carries a data channel over SCTP over DTLS over UDP. No audio, no video.a=ice-ufrag/a=ice-pwd: short-lived credentials that authenticate the connectivity checks in Step 3, so a stray packet from elsewhere cannot hijack the path.a=ice-options:trickle: “I will send you my network addresses as I discover them, do not wait for the full list.”a=fingerprint:sha-256 …: the hash of the certificate this browser will present in the DTLS handshake. This line is the trust anchor of the whole system: the other side will refuse any certificate that does not hash to exactly this value.a=setup:actpass: the offerer is willing to be either the DTLS client or server; the answerer picks.a=max-message-size:262144: the largest single message this browser will accept on the channel: 256 KiB in Chrome. This number is why file transfer protocols chunk (Step 5).
The receiver replies with an answer in the same format, carrying its own credentials and its own certificate fingerprint. Both documents pass through signaling. Note that the offer contains no address yet (0.0.0.0) — addresses come next, separately.
Step 3: Path finding with ICE
Both devices usually sit behind routers that translate addresses and drop unsolicited incoming traffic. Neither browser knows how it looks from outside, and neither can be dialled directly. ICE solves this by gathering every address a browser might be reachable at, exchanging the lists, and testing pairs until one works.
| Candidate type | What it is | Where it comes from | If it wins, the badge says |
|---|---|---|---|
| host | The device's own address on its local network, e.g. 192.168.1.23:51234 | The network interface | LAN direct |
| srflx (server-reflexive) | The public address:port the router presents for this browser | A STUN server reports what it saw | Internet direct |
| prflx (peer-reflexive) | An address discovered mid-check, when a probe arrives from somewhere unexpected | The connectivity checks themselves | Internet direct |
| relay | An address on the TURN server allocated for this browser | The TURN server | Encrypted relay |
Candidates are sent to the other side as they are found (trickle ICE), and each browser pairs its local candidates with the remote ones and ranks the pairs. The ranking prefers host over reflexive over relay, so if a local path exists it is tried first. Then come the connectivity checks: small STUN packets sent along each pair, authenticated with the credentials from the SDP. A pair that answers is valid; the highest-priority valid pair is nominated and carries the connection. On two home networks this takes well under a second; the hole-punching that makes it possible (both sides sending outward at once so each router sees a “reply”) is explained in NAT types and STUN, TURN and ICE.
ShareDrop.org reads the nominated pair from the live connection statistics and shows it as the status badge. It is never inferred from configuration: if the badge says LAN direct, both sides of the winning pair were host candidates.
Step 4: The DTLS handshake, keys that never leave the browsers
With a path open, the two browsers run a DTLS handshake across it: the datagram cousin of the TLS that secures every HTTPS site. Each side presents a certificate it generated locally, and each side checks the certificate against the fingerprint it received in the SDP. If they match, the handshake derives session keys that exist only in the two browsers' memory. If they do not match, the connection is torn down.
Two consequences follow. First, a TURN relay in the path is forwarding ciphertext it cannot open, because the keys were never sent anywhere: the relay saw the encrypted handshake go past like any other packet. Second, the fingerprint in the SDP is the one thing an attacker who controlled signaling could tamper with: substitute both fingerprints, and each browser would happily complete a DTLS session with the attacker instead of with each other. That is exactly the case the six-digit safety code exists to catch, because it is derived from both fingerprints and would differ on the two screens.
Step 5: The data channel, SCTP inside DTLS
Inside the encrypted DTLS tunnel runs SCTP, a transport protocol that predates WebRTC and provides the properties a file transfer wants: reliable delivery, ordering, congestion control that behaves fairly next to TCP across the internet, and message boundaries. A data channel is one SCTP stream with a label, opened with a small DCEP handshake. ShareDrop.org opens its channel ordered and reliable, the defaults, because a file with chunks missing or reordered is not a file.
Three properties of the channel shape everything built on top of it:
- Maximum message size. Chrome advertises 256 KiB; Firefox and Safari differ. A protocol that wants to work between any two browsers has to stay below the smallest common limit. ShareDrop.org uses 64 KiB chunks, which also gives fine-grained progress and small units for hashing and resume.
bufferedAmount. The channel exposes how many bytes are queued but not yet sent. A sender reading from a fast disk can fill this queue far faster than a network drains it; unchecked, the queue grows until the tab dies. The fix is backpressure: pause reading when the queue exceeds a high-water mark (8 MiB), resume on thebufferedamountlowevent when it falls under a low-water mark (1 MiB). Sender memory then stays bounded whether the file is 10 MB or 100 GB (why this matters so much).- Congestion control. SCTP slows down when the network is congested and speeds up when it is not, the same way TCP does. This is why a transfer across the internet settles at the sender's uplink rate without anyone configuring it.
Step 6: The file protocol on top
A data channel moves messages; a file transfer needs a protocol. ShareDrop.org's uses two kinds of message: small JSON control messages and binary chunk messages.
- Manifest. The sender lists what it intends to send: file names, sizes, types, and for folders the relative path of each file. Nothing else is sent until the receiver explicitly accepts. This is the second gate (the first was the connection prompt): the receiver sees exactly what is coming and how big it is.
- Accept. The receiver replies, having checked free space and, in browsers that assemble files in memory, that each file is under the 512 MB cap. A refusal here is why an oversized file is rejected up front rather than failing at 97%.
- Chunks. Each 64 KiB chunk carries a small binary header (transfer ID, file ID, chunk index) followed by the payload. The header lets the receiver place every chunk unambiguously even if a reconnect interleaves old and new traffic.
- Per-file completion. After a file's last chunk, the sender sends the file's SHA-256. The receiver compares it with the hash it has been computing as chunks arrived and replies with a verified acknowledgement or a failure.
- Transfer completion. When every file is acknowledged, both sides mark the transfer done. Only now does the badge say complete.
Folder structure rides on the manifest: because each file carries its relative path, a receiving browser that can create directories (Chrome and Edge on desktop) recreates the tree; others receive the files flat with the path folded into the name (details).
Step 7: Verification, for free
The expensive way to verify a file is to hash it after it arrives: a second full pass over the data, which for a multi-gigabyte file on a laptop disk means a long pause at 99%. ShareDrop.org hashes incrementally: each chunk is fed into a running SHA-256 on both the sending and receiving side as it passes, so when the last chunk lands the digest is already known and the comparison is instant. The cost is spread across the transfer, and in ShareDrop.org's benchmark it is invisible: the full pipeline sustains 200+ Mbps on one machine with hashing running on both ends (measurements).
A mismatch is reported as a failure, never hidden. A tool that says “done” without a check has no way to know whether the file is intact; a tool that says “done” despite a mismatch is worse than no tool (why an unverified file is never marked done).
Step 8: Resume after a dropped connection
Phones switch from Wi-Fi to cellular; laptops sleep; routers reboot. When the channel drops, the two browsers reconnect through signaling (the room still exists), and negotiate a new connection. The receiver reports, per file, the index of the last chunk it verified and wrote; the sender resumes from the next one. Nothing already on disk is resent.
One honest detail: a reconnect means new DTLS certificates, which means a new safety code. ShareDrop.org resets the “verified” state and shows the new code rather than silently carrying the old confirmation forward — a small annoyance that prevents a real attack.
A worked example: 2 GB from a phone to a laptop
Putting the steps together with real numbers. A phone on home Wi-Fi sends a 2 GB video to a laptop on the same network, the laptop receiving in Chrome.
- 0.0 s: The phone creates a room. Signaling replies with a code; the QR appears. The phone begins gathering candidates immediately: its Wi-Fi address (host), and in parallel it asks STUN and requests a relay allocation.
- ~3 s: The laptop scans the code and joins. The phone shows “A device wants to connect”; the user accepts; the offer goes across.
- +0.2 s: The laptop answers. Both sides now hold each other's fingerprints and first candidates. Connectivity checks begin on the host pair, the two Wi-Fi addresses, before the relay allocation has even completed.
- +0.3 s: The host pair succeeds and is nominated. The DTLS handshake runs across it: two round trips on a local network, a few milliseconds each. The badge reads LAN direct. The relay allocation, now finished, will never be used and will expire on its own.
- +0.4 s: The phone sends the manifest: one file, 2,147,483,648 bytes,
IMG_0412.MOV. The laptop checks free space, prompts once for a save location, and accepts. - +0.5 s to ~85 s: 32,768 chunks of 64 KiB flow across. The phone reads ahead until 8 MiB is queued, pauses, resumes at 1 MiB; the laptop writes each chunk to the chosen file as it arrives and feeds it into SHA-256. On a Wi-Fi 5 network the rate settles around 25 MB/s, so the transfer takes roughly a minute and a half. Memory on both sides stays under a few tens of megabytes for the whole duration.
- ~85 s: The phone sends the final hash. The laptop's running digest already matches; it acknowledges. Both screens show done, with the safety code still displayed so it can be compared after the fact.
Change one variable (the laptop is at the office instead), and steps 3 and 4 differ: the host pair fails, the reflexive pair succeeds a few hundred milliseconds later, the badge reads Internet direct, and step 6 now takes as long as the phone's upload allows: at 30 Mbps, about nine minutes. Everything else is identical.
What is different on a phone
The ShareDrop.org iOS and Android apps speak the same protocol as the web page (the same signaling, the same chunk format, the same hashing), so an app can talk to a browser and vice versa. Two things differ. Native apps write received files to storage as they arrive, so the 512 MB cap that applies to Safari and Firefox does not exist there. And phones suspend apps aggressively: a locked screen can pause the transfer, which is why the apps keep the screen awake while a transfer runs and resume from the last verified chunk if the system suspends them anyway. Between two phones with no router (in a field, on a train), one phone's hotspot makes a private network and the transfer runs LAN direct over it.
Where the time goes
| Phase | Typical duration | What sets it |
|---|---|---|
| Room creation and join | Under a second | Signaling round trips |
| Offer / answer / ICE | 0.3–2 seconds | How many candidate pairs must be tried; slower when a relay is the only route |
| DTLS handshake | One or two round trips | Latency of the chosen path |
| Chunk transfer | File size ÷ link speed | Router on a LAN; the slower uplink across the internet (diagnosing this) |
| Verification | ~0 | Incremental hashing; nothing left to do at the end |
| Finalisation | 0 on Chrome/Edge desktop; seconds elsewhere | Whether the browser streamed to disk or must assemble the file in memory |
What can go wrong, and at which step
| Symptom | Step | Usual cause |
|---|---|---|
| “Code not found” | 1 | Room expired (codes live minutes) or a typo; make a new one |
| Stuck on “connecting” | 3 | No candidate pair works and the relay is blocked too, usually a corporate firewall or a VPN (the list) |
| Badge shows relay on the same Wi-Fi | 3 | Client isolation on the access point (hotels and offices) |
| Safety codes differ | 4 | Reconnected session (new code is normal), or, rarely, a tampered introduction; stop and re-invite |
| File refused before it starts | 6 | Over 512 MB on a browser that cannot stream to disk, or not enough free space |
| Checksum failure | 7 | Corruption on one side's storage; retry, and suspect the disk if it recurs |
| Sits at 99% | Finalisation | The receiving browser is assembling or importing the file (what is happening) |
What the servers never learn
In this architecture the signaling server learns that two anonymous browsers connected and exchanged negotiation messages. It does not learn file names, sizes or contents: those travel only inside the encrypted channel. Even when a relay must forward the traffic, it forwards ciphertext: the encryption keys were negotiated directly between the two browsers (how that works). There is no upload endpoint to breach, because nothing is uploaded.
Why a browser is the right client for this
Every phone, laptop and desktop already ships one, kept up to date by its vendor, with WebRTC, DTLS, SCTP and SHA-256 built in and audited far more heavily than any transfer app could afford. A page loaded fresh each time cannot install a persistent agent, cannot read files you did not pick, and cannot open a port the browser sandbox does not allow. The trade is that a browser tab has to stay open and, in some browsers, has to hold a received file in memory. For the majority of transfers between two people who are both present, that trade is a good one.
The honest caveats
- Both browsers must stay open for the duration — there is no store-and-forward.
- Some networks make direct connection impossible; a relay adds a hop (still encrypted), and a network that blocks even relay ports blocks the transfer.
- The receiving device ends up with a copy of the file. Encryption in transit says nothing about what happens after arrival.
- The peer learns your IP address on a direct connection: that is what a direct connection is. A relay hides it, at the cost of a hop.
Those trade-offs are inherent to the model, not bugs. In exchange you get transfers with no upload step, no storage to breach later, and no account to create.
Standards referenced
- RFC 8825: Overview: Real-Time Protocols for Browser-Based Applications (the WebRTC architecture).
- RFC 8829: JSEP: how offers and answers are created and applied.
- RFC 8445: ICE: candidate gathering, pairing and connectivity checks.
- RFC 8838: Trickle ICE.
- RFC 8827: WebRTC Security Architecture: fingerprints, DTLS and end-to-end keys.
- RFC 8831: WebRTC Data Channels: SCTP over DTLS, message-size limits.
- RFC 8832: DCEP: the in-band handshake that opens a channel.
- RFC 8841: SDP for SCTP-based media transport (the
sctp-portandmax-message-sizelines). - NIST FIPS 180-4: SHA-256.