A Developer’s Guide to ICE, STUN, and TURN in the Protoface REST API

Developer guide to ICE, STUN, and TURN for Protoface WebRTC avatars, with debugging tips for direct and relay connections.
Introduction
If you are building a realtime avatar, you eventually run into the same networking problem every WebRTC application hits: “Why does it connect instantly on my laptop, but fail for some users, some offices, or some mobile networks?” The short answer is that direct peer-to-peer media paths are not always possible, and the network path you actually get depends on NATs, firewalls, and what traversal candidates the browser and server can agree on.
This is where ICE, STUN, and TURN matter. They are not “extra WebRTC features”; they are the mechanism that gets audio and video flowing at all. In this post, I’ll explain how they fit together, how to reason about connection behavior in realtime avatar systems, and how to debug the common failure modes. By the end, you should be able to read a WebRTC connection trace, understand why an avatar session is using a relay instead of a direct path, and know what to check when a user says “the call never starts.”
ICE is the connection negotiation layer
ICE stands for Interactive Connectivity Establishment. It is the algorithm WebRTC uses to find a working network path between endpoints. The important point is that ICE does not mean “always direct.” It means “try all viable paths and select the one that works.”
In practice, each side gathers a set of candidates:
Host candidates: local addresses on the machine or device.
Server-reflexive candidates: public-facing addresses discovered via STUN.
Relay candidates: addresses on a TURN server that can forward media.
ICE then runs connectivity checks across candidate pairs and picks a nominated route. For a developer, the useful model is:
Collect candidates.
Test candidate pairs.
Pick the first pair that can reliably carry media.
That is why your app may work over a direct UDP path at home, but fall back to a relay in a corporate network. The application behavior is the same, but the transport is different.
STUN tells you what the network can see
STUN is the lightweight protocol used to discover your public-facing address as seen from outside your NAT. A STUN server does not relay media; it simply answers the question “what source IP and port do I appear to have on the internet?”
That matters because many devices are behind NATs that rewrite addresses and ports. A browser can know its local IP, but that is often useless to the remote peer. STUN gives ICE the server-reflexive candidate needed to try a direct connection.
There are a few practical limits to keep in mind:
STUN can help discover reachability, but it cannot punch through all network policies.
Symmetric NATs and strict firewalls often break direct connectivity.
STUN is cheap and fast, so it is worth trying first, but it is not a guarantee.
When developers say “WebRTC is P2P,” they usually mean “WebRTC prefers direct paths when available.” In reality, the transport outcome is determined by what the network permits.
TURN is the fallback that makes hard networks work
TURN is a relay. If a direct path cannot be established, ICE can select a TURN candidate, and all media flows through the TURN server. This adds latency and operational cost, but it is often the difference between “works for most users” and “works for users behind restrictive networks.”
For realtime avatars, TURN is especially important because media continuity is more important than theoretical optimality. A slightly longer path is better than no path. In customer support, sales, or embedded web experiences, a dead session is worse than a relayed session every time.
The trade-off is straightforward:
Direct path: lower latency, lower cost, but not always possible.
TURN relay: higher latency and bandwidth cost, but much higher success rate.
From a debugging perspective, if a session only works when you are on a home network but fails on office Wi-Fi, TURN is usually the first thing to verify. If it works on TURN and fails without it, the problem is almost always network policy, not your application logic.
How to think about ICE state during an avatar session
In a realtime avatar system, the control plane and media plane are different concerns. Creating a session via REST is one step; actually establishing the media transport is another. The session can exist in your backend, while the browser or client still negotiates ICE in the background.
The states that matter operationally are:
Gathering: candidates are being discovered.
Checking: candidate pairs are being tested.
Connected: a route is nominated and media can flow.
Failed: no candidate pair worked in time.
When something stalls, ask which stage you are in. If candidate gathering never completes, you are likely blocked on STUN/TURN access. If gathering finishes but checks fail, the issue is usually firewall policy or an unsupported network path. If the session connects and then drops, look at transport stability and NAT rebinding behavior.
One common mistake is to treat ICE failures as application failures. They are usually network-path failures. The application may be fine; the path is not.
Debugging the usual failure modes
Here is the practical checklist I use when diagnosing WebRTC connectivity issues for avatar workflows:
Confirm the media path is actually being negotiated. A REST session can be created successfully even if media never connects.
Check whether a TURN relay is being used. If the network is restrictive, relay may be the only working option.
Look for blocked UDP. Many enterprise networks allow HTTPS but restrict UDP, which hurts WebRTC’s preferred media path.
Verify that STUN/TURN endpoints are reachable. DNS, TLS, or outbound filtering issues can make candidate gathering fail early.
Inspect ICE state transitions. Stuck in gathering usually points to server reachability; stuck in checking often points to candidate incompatibility.
If you are integrating from a browser, browser devtools and WebRTC internals are often enough to confirm whether you are seeing host, srflx, or relay candidates. For server-side voice agents, your SDK or framework logs should expose the same basic state transitions.
Where Protoface fits
For an avatar application, you generally want the media plumbing to disappear into the integration surface you are already using. With Protoface, the REST API is the part you use to create and manage avatars and realtime sessions, while the media side is handled through the WebRTC stack behind the scenes. The practical benefit is that you can focus on the agent logic and session lifecycle instead of hand-rolling connectivity infrastructure.
A simple REST session creation flow looks like this at a high level:
The exact request shape depends on the endpoint, but the operational idea is the same: you create a session in the API, then the client connects and negotiates media. If the network path is poor, ICE will still try host, STUN-derived, and TURN-relayed candidates in the normal WebRTC way.
If you are integrating a Python backend, the SDK is the more ergonomic place to do session orchestration. The code below is intentionally illustrative; check the docs for the exact method names and fields.
If you are embedding Protoface into a LiveKit agent, the plugin route is often the cleanest path. The agent keeps handling speech, turn-taking, and transcription; the avatar layer gives it a synchronized visual presence. The useful part here is that the plugin hides the media plumbing, but you still benefit from the same ICE/STUN/TURN behavior underneath. For examples, see the plugin repository and the quickstarts linked from the main docs.
For deeper implementation details and field-level references, start with the documentation at docs.protoface.com. If you are specifically working on the LiveKit side, the plugin repo is the best place to see how the avatar layer gets wired into an existing voice agent.
Conclusion
ICE is the negotiation process, STUN helps discover public reachability, and TURN provides the fallback relay when the network refuses a direct path. For realtime avatar systems, that trio is not an implementation detail; it is the difference between a session that connects reliably and one that mysteriously fails on some networks.
The main thing to internalize is this: if your avatar or voice session fails, do not jump straight to application logic. First determine whether the transport path was established, which candidate type was selected, and whether a relay was required. That usually cuts debugging time dramatically.
If you are building with Protoface, the next concrete step is to read the docs, try a quickstart, and confirm that your session flow behaves as expected across a normal network and a restrictive one. Once you can explain which ICE path is being used, you are past the hard part.
