About WebRTC Peer-to-Peer File & Text Transfer
WebRTC Peer-to-Peer File & Text Transfer opens a direct, encrypted data channel between two browsers and streams files, whole folder trees, and text messages across it. The payload bytes travel from one browser straight to the other; the only thing this site can ever carry is the short session-setup message, and in manual-code mode not even that.
Every WebRTC session has two unrelated halves. Session setup is a chicken-and-egg problem — two browsers that have never met must swap an SDP offer, an answer, and a list of ICE candidates — and the specification deliberately does not say how, which is why every product invents its own signalling. Once the descriptions are in place the browsers talk to each other directly: SCTP inside DTLS on top of UDP, with the DTLS keys negotiated end to end and the certificate fingerprints carried inside the SDP. This tool creates the channel with ordered delivery and no retransmit limit, so it is a fully reliable ordered stream with the same guarantees as TCP; the unordered and partially reliable variants of RTCDataChannel exist for real-time telemetry, not for bytes that must arrive intact. Files are sent in 16 KB chunks with backpressure applied at 2 MB of buffered data and released at 256 KB, which keeps a fast sender from ballooning the sending tab's memory.
How to use this developer tool
- Open the tool on both devices. In room mode, create a room on the first device and pass the eight-character code, the invite link, or the QR code to the second, which enters it to join.
- Prefer manual codes when you do not want a server involved at all: generate the invite code, hand it over by any channel you already trust, paste the reply code back, and the handshake completes without a single request to this site.
- Add your own STUN or TURN servers when the two devices are not on the same network. Enable relay-only mode if you want no local or reflexive addresses in the exchange at all.
- Choose a receive folder on the receiving device before sending anything large. Without one the incoming data is buffered in browser memory and capped at 512 MB.
- Drag in files or whole folders, or type a message of up to 32 KB. Folder structure is preserved as relative paths on the receiving side.
- Watch the route readout while the transfer runs: it names the candidate types that won, the protocol, the round-trip time, and whether the path ended up relayed.
Worked example: a room-code handshake, request by request
Device A creates a room, device B joins with the code, and the two swap an offer, an answer, and trickled candidates. This is the whole conversation with the server; every data value is an opaque deflated payload, shortened here.
A -> POST /api/signal {"action":"create"}
A <- 200 {"mode":"room","room":"7KQ3M8ZP","peer":"a41f0c9e73b25d68","token":"c7f1...","peers":1,"expiresIn":600000}
B -> POST /api/signal {"action":"join","room":"7KQ3M8ZP"}
B <- 200 {"mode":"room","room":"7KQ3M8ZP","peer":"1d77be04a9c3f512","token":"5b90...","peers":2,"expiresIn":600000}
A -> POST /api/signal {"action":"send","room":"7KQ3M8ZP","peer":"a41f...","token":"c7f1...","data":"DFX1.eJyVVMtu2zAQ_BV..."}
A <- 200 {"delivered":1,"peers":2}
B -> POST /api/signal {"action":"poll","room":"7KQ3M8ZP","peer":"1d77...","token":"5b90..."}
B <- 200 {"messages":[{"seq":1,"data":"DFX1.eJyVVMtu2zAQ_BV..."}],"peers":2}Decoded first DFX1 payload (the offer):
{"kind":"offer","type":"offer","sdp":"v=0 ... a=fingerprint:sha-256 8C:1F:2B:...:A0
a=setup:actpass
a=mid:0
a=sctp-port:5000
a=max-message-size:262144"}
Decoded trickled candidate, one per message:
{"kind":"candidate","candidate":{"candidate":"candidate:842163049 1 udp 1686052863 203.0.113.44 54123 typ srflx raddr 0.0.0.0 rport 0","sdpMid":"0","sdpMLineIndex":0}}
Result on both devices:
route srflx -> srflx over udp, 38 ms round trip, relayed: no
data channel devtoys-transfer, ordered, reliable, open
first file report-2026-q1.pdf, 41.2 MB, streamed in 16 KB chunksThe mailbox only ever handles the DFX1 strings: it forwards them in order, never inspects them, and forgets the room when both peers leave. Those strings do nonetheless contain 203.0.113.44, which is exactly why manual-code mode exists for people who would rather not put their candidates through any server at all.
NAT behaviour and whether a direct path survives
What matters is how your router assigns the external port. The first three behaviours keep one mapping per internal socket regardless of destination, so the address a STUN server reported is the address your peer can actually use. The last one does not, and that is when a relay stops being optional.
| NAT behaviour | External port mapping | Who may send inbound | Direct connection? |
|---|---|---|---|
| Full cone (endpoint-independent) | One external port per internal socket, reused for every destination | Any host, once the mapping exists | Yes, and it is the easiest case there is |
| Address-restricted cone | Same external port for every destination | Only hosts you have already sent something to | Yes — the ICE connectivity checks from both sides create the permission |
| Port-restricted cone | Same external port for every destination | Only the exact address and port you sent to | Yes, provided both sides probe. This is the common home-router case |
| Symmetric (address-and-port-dependent) | A fresh external port for every destination | Only the exact address and port you sent to | No. The port the STUN server saw is not the port your peer will see, so the srflx candidate is useless and TURN is required |
| Carrier-grade NAT or double NAT | Usually symmetric at the outer layer, and not under your control | Effectively nobody | Usually not. Mobile data and some ISP fibre deployments land here; plan on a relay |
Troubleshooting
A large transfer starts fast, then the receiving tab stalls and finally reports a memory limit.
Why it happens With no receive folder chosen, incoming chunks accumulate in browser memory. The session caps that at 512 MB and aborts the file deliberately, rather than letting the operating system kill the tab and lose everything.
Fix Choose a receive folder before starting. The File System Access directory picker lets the tool write each chunk straight to disk, so the limit becomes free space rather than RAM. Only Chromium-based browsers expose that picker today; on Firefox or Safari, split the file or use a desktop tool for very large transfers.
Both sides join the room, but the channel never opens and diagnostics report a stall after twelve seconds.
Why it happens Typically symmetric NAT on both ends, or a network that drops UDP: with only STUN configured, no candidate pair can pass the connectivity checks. The browser may sit in "checking" for a very long time before admitting failure, so the tool calls it stalled at twelve seconds rather than spinning forever.
Fix Add a TURN server with credentials on both devices — this site does not run a relay, so bring your own coturn or a commercial one — and verify it on the WebRTC Checker page before retrying here.
The room code is reported as not found although the other side created it seconds ago.
Why it happens Two very different situations look the same. The mailbox lives in process memory, so a deployment running more than one front-end instance can land the two peers on different processes; separately, a room that sat idle for ten minutes is already gone.
Fix Operators need session stickiness on /api/signal, hashed by client IP or cookie, and a reverse proxy that lets a poll hang for about 25 seconds with response buffering disabled on that path. As a user, create a fresh room, or switch to manual codes, which never touch the server at all.
On a corporate network nothing connects, even with a TURN server configured.
Why it happens UDP is dropped outright, so an allocation against turn:host:3478 never completes and neither does any host or reflexive path.
Fix Use a turns: URL on port 443 with ?transport=tcp. TURN over TLS on 443 is indistinguishable from ordinary HTTPS to most middleboxes. Expect noticeably lower throughput, because every byte is relayed, framed, and then re-encrypted by DTLS on top.
A manual invite code is rejected as invalid, or it is far too long to paste anywhere.
Why it happens Manual mode waits for ICE gathering to finish and packs the complete SDP into a single code, so a machine with many interfaces plus a relay candidate produces a long string. Chat clients wrap, shorten, or truncate it, and a truncated code fails at the decompression step.
Fix Pass the code through something that preserves it byte for byte — a file, a clipboard sync, a QR scan. The tool only offers a QR code below about 1800 characters, which is a fair signal that the code has grown too big for a messaging app.
The connection is direct but throughput sits well below the link speed.
Why it happens Data moves in 16 KB chunks with SCTP backpressure at 2 MB buffered, the sender reads each slice from disk, and every chunk is DTLS-encrypted. On a relayed path each byte also makes an extra round trip through the TURN server.
Fix Check the route readout: if it says relayed, the ceiling is your TURN server's bandwidth, not your link. On the same LAN, remove public STUN entries so the host candidates win quickly and the path stays local.
When this tool is the right choice
- Moving a large file between two machines you are sitting at, on the same network, without a USB stick and without uploading it anywhere first.
- Sending a folder tree to a colleague you are already talking to, when both of you are online at the same moment.
- Getting a file off a locked-down machine that can open a browser but cannot install software or mount external storage.
- Seeing your own NAT situation in practice: the route readout states plainly whether the connection ended up direct or relayed.
When to use something else
- Asynchronous delivery. Both browsers must be open at the same time, so if the other person will collect the file tomorrow, use a managed file service or object storage with a signed URL instead.
- Regulated or auditable transfers. There is no delivery receipt, no retention, no malware scanning, and no access log here; use a managed secure-transfer product when you must be able to prove what was sent.
- Distributing one file to several recipients. A room holds two members, so use a file service, or repeat the transfer once per peer.
Privacy and data handling
In room mode the two browsers exchange session descriptions through a same-origin POST /api/signal mailbox. That mailbox keeps rooms in the SSR process's memory only: at most two members per room, at most 96 KB per message, released as soon as both sides leave or after ten idle minutes, with a ceiling of 100 rooms per visiting IP per day and 600 signalling requests per minute. Nothing is written to disk and the server never parses a payload — but be precise about what that does and does not mean. The request carries your IP like any HTTP request, the room code is visible to the server, and the opaque string it forwards is a compressed SDP that contains your ICE candidates, which is to say IP addresses, plus your DTLS fingerprint. File and message bytes never enter it. Manual-code mode does not contact the server at all: the invite and reply codes are deflated, Base64url-encoded SDP that you carry across yourself. ICE server URLs and credentials stay in this browser. This is a diagnostic-grade peer-to-peer tool, not a managed secure-transfer product: DTLS protects the channel, but nobody here keeps a delivery receipt, an audit log, or a copy of anything on your behalf.
Related developer tools
Frequently asked questions
Do my files pass through your server?
No. Files, folders, and text move over the direct WebRTC data channel between the two browsers and never reach this site. In room mode the server relays only the short session-setup strings, which it forwards without parsing and keeps in memory for at most ten idle minutes. In manual-code mode it is not contacted at all.
Is there a file size limit?
There is no protocol limit, because the transfer streams in 16 KB chunks rather than loading a file into memory. The practical bound is on the receiving side: pick a receive folder and it is your free disk space, skip that step and incoming data is buffered in browser memory with a hard cap of 512 MB. Text messages are limited to 32 KB.
Why would I need a TURN server?
STUN only tells each side what its public mapping is; that is enough as long as at least one side can be reached through its NAT. When both ends sit behind symmetric NAT or carrier-grade NAT, or when the network drops UDP, no direct pair can pass and a TURN relay has to carry the session. This site does not host one, so you configure your own and keep control of the relay.
Does it work between a phone and a laptop?
Yes, and the QR code exists for exactly that: scan it on the phone to open the room without typing the code. On the same Wi-Fi the connection is usually direct and fast. Over mobile data, expect carrier-grade NAT, which often means you will need a TURN server for the connection to establish at all.
Does it work on a LAN with no internet access?
Partly. Both devices must first load the page, which needs to reach this site, so keep the tabs open if the link is about to go away. After that, manual codes complete the handshake without any server, and mDNS host candidates resolve over multicast DNS on the local subnet, so the transfer itself runs entirely on your LAN. Room mode does need the site's signalling endpoint and will not work offline.
Is the transfer really end-to-end encrypted?
The data channel is protected by DTLS, negotiated directly between the two browsers, and the certificate fingerprints are exchanged inside the SDP. Be aware of what that implies in room mode: the mailbox forwards those fingerprints, so a signalling server that tampered with an offer could in principle interpose itself. Manual codes exchanged over a channel you already trust remove that possibility entirely.
How long does a room code last?
A room is held in the server process's memory and expires after ten idle minutes, or immediately once both members leave. It stays alive for the whole session so either side can trigger a reconnect over the same code after a stall. The code is eight characters from a 32-symbol alphabet that omits look-alike characters such as 0, O, 1, and I.