How Browser-to-Browser File Transfer Actually Works

By ShareDrop.org Engineering · Published · Updated

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:

Sequence diagram of a browser-to-browser transfer: sender creates a room on the signaling server; receiver joins with the short code; offer and answer with certificate fingerprints pass through signaling; both browsers ask STUN for their public addresses and trickle ICE candidates; connectivity checks find a direct path; the DTLS handshake runs end to end; a manifest is accepted; 64 KiB chunks flow directly with SHA-256 computed on both sides; hashes are compared and the transfer completes
The whole transfer in one picture. Everything above the dashed line goes through servers and contains no file data; everything below it goes browser to browser.

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:262144

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 typeWhat it isWhere it comes fromIf it wins, the badge says
hostThe device's own address on its local network, e.g. 192.168.1.23:51234The network interfaceLAN direct
srflx (server-reflexive)The public address:port the router presents for this browserA STUN server reports what it sawInternet direct
prflx (peer-reflexive)An address discovered mid-check, when a probe arrives from somewhere unexpectedThe connectivity checks themselvesInternet direct
relayAn address on the TURN server allocated for this browserThe TURN serverEncrypted 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:

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.

  1. 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.
  2. 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%.
  3. 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.
  4. 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.
  5. 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.

  1. 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.
  2. ~3 s: The laptop scans the code and joins. The phone shows “A device wants to connect”; the user accepts; the offer goes across.
  3. +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.
  4. +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.
  5. +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.
  6. +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.
  7. ~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

PhaseTypical durationWhat sets it
Room creation and joinUnder a secondSignaling round trips
Offer / answer / ICE0.3–2 secondsHow many candidate pairs must be tried; slower when a relay is the only route
DTLS handshakeOne or two round tripsLatency of the chosen path
Chunk transferFile size ÷ link speedRouter on a LAN; the slower uplink across the internet (diagnosing this)
Verification~0Incremental hashing; nothing left to do at the end
Finalisation0 on Chrome/Edge desktop; seconds elsewhereWhether the browser streamed to disk or must assemble the file in memory

What can go wrong, and at which step

SymptomStepUsual cause
“Code not found”1Room expired (codes live minutes) or a typo; make a new one
Stuck on “connecting”3No 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-Fi3Client isolation on the access point (hotels and offices)
Safety codes differ4Reconnected session (new code is normal), or, rarely, a tampered introduction; stop and re-invite
File refused before it starts6Over 512 MB on a browser that cannot stream to disk, or not enough free space
Checksum failure7Corruption on one side's storage; retry, and suspect the disk if it recurs
Sits at 99%FinalisationThe 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

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