Building Online Games with UE5 WebRTC4Unreal
EOS and Steam P2P Miss the Point
Steam’s native lobby P2P features depend heavily on Steam accounts. EOS P2P and lobbies don’t require an Epic Games account and can be combined with each platform’s accounts or your own authentication, but they still depend on EOS-specific user IDs, relays, and lobby infrastructure, so vendor lock-in remains at the service infrastructure level. You’re also locked into the vendor’s servers, which means you can’t control what happens during outages or maintenance.
WebRTC
Looking beyond the game world, there’s a P2P standard called WebRTC. It’s a protocol established for real-time use cases like video conferencing, covering TURN and NAT traversal (discussed below). Riding on it is easier than implementing something game-specific, so that’s what I used.
I Made a Plugin Called WebRTC4Unreal
As you can see from the EOS code, UE is highly abstracted, so if you swap out the NetDriver and NetConnection, the application layer above them runs without any particular changes.
https://github.com/mizuamedesu/WebRTC4Unreal
For details, Riku Ishikawa’s slides are an easy read. https://www.docswell.com/s/strvert/K7V3JJ-unrealengine-network-architecture#p32
In a normal Listen Server, IpNetDriver carries packets over UDP. In WebRTC4Unreal, a custom WebRTC4UnrealNetDriver receives the same packets and hands them to a WebRTC DataChannel. On the receiving side, the byte stream pulled from the DataChannel is returned to the corresponding UNetConnection. From the game’s perspective, only the transport changed from UDP to WebRTC.
There’s also no point reinventing the wheel: Unreal Engine ships libwebrtc for Pixel Streaming, so I used that.

Supporting Multi-Cloud
Making the plugin exclusive to one service would just create another vendor lock-in. So in WebRTC4Unreal, I separated the Provider that creates and resolves rooms from the Transport that carries Unreal packets, using interfaces.
The initial release includes implementations for Cloudflare and HeteroCloud. As described above, it can be extended further.
Implementing with HeteroCloud Flow
https://heterocloud.mizuame.app
HeteroCloud is a next-generation cloud I’m developing, spanning everything from the overlay network to the cloud platform and services. It’s an ongoing result of my work as a visiting researcher at the Cyber Technology Laboratory of the IPA Industrial Cyber Security Center, and all sources are published under the MIT license. HeteroNetwork can connect machines sitting inside different LANs and NATs — home, server rooms, cloud — as a single secure overlay network.
With this mechanism, machines that differ in location, network line, performance, and owner can be used as one platform while reachability is determined automatically. In particular, it can bundle surplus PCs and small servers scattered across sites that are hard to use on their own and put them to work as Kubernetes compute resources, which makes it possible to offer services at prices no cloud vendor can achieve.
https://github.com/orgs/IPA-CyberLab/repositories
Flow, a HeteroCloud service, provides WebRTC functionality, and everything from signaling to TURN is free. It’s offered through OpenAPI, so if you hand an LLM the JSON, it will probably implement it for you.
https://flow.heterocloud.mizuame.app/openapi.json https://flow.heterocloud.mizuame.app/docs/
Understanding the Flow
I don’t think a code walkthrough is necessary, so I’ll skip it, but here’s the flow:
- Long-lived developer credentials are held only by the backend server. The backend requests HeteroCloud to issue short-lived access credentials, with minimal permissions and an expiration, for each Unreal client.
- The room creator uses its short-lived access credentials to create a room in p2p mode. After creating it, the creator joins the room too and starts the Unreal side as a Listen Server.
- Joining clients also obtain their own short-lived access credentials and join the room using the room ID shared by the creator.
- Host and Client connect to Flow’s signaling server and exchange SDP Offer, SDP Answer, and ICE Candidates.
- ICE attempts a direct connection using STUN. Only when a direct connection isn’t possible due to NAT or firewall conditions does it automatically fall back to HeteroCloud’s TURN server.
- Once the WebRTC DataChannel opens, WebRTC4Unreal’s NetDriver sends and receives Unreal Replication and RPC packets over the DataChannel.
For this demo, I stood up a simple backend server locally. Normally, authentication via an IdP or similar would be involved when issuing temporary tokens to clients.

Implementing with Cloudflare
Cloudflare can use the same WebRTC4Unreal Core as the HeteroCloud Flow version. However, whereas Flow provides rooms, signaling, STUN, and TURN as a single integrated API, the Cloudflare version builds the connection infrastructure by combining multiple services.
This CloudflareDirectProvider uses the following three:
- Cloudflare Workers: an HTTP API that accepts room creation and join requests from Unreal clients
- Durable Objects: per-room state management and WebSocket signaling
- Cloudflare TURN: a relay server used when a direct connection fails
Durable Objects assign a unique instance per ID, which suits use cases like rooms where multiple clients share the same state. Here, the room ID determines the DirectRoom Durable Object, and room info, host, participants, max participants, and expiration are stored in SQLite.
A random join token is issued per participant, but only its hash is stored in the Durable Object. Each client connects to the signaling WebSocket with its own join token and exchanges SDP Offer, SDP Answer, and ICE Candidates. The WebSocket uses the Hibernation API, so idle Durable Objects can be hibernated while connections stay alive. When a room expires, an Alarm discards the WebSockets and stored state.
Understanding the Connection Flow
- When the Host requests room creation from the Worker, the Worker issues a room ID, a participant ID for the Host, and a join token, and creates the Durable Object for that room.
- The Host connects to the signaling WebSocket and starts the Unreal side as a Listen Server. The screen shows only the room ID to share with the other party.
- The Client pastes the received room ID into the join field and requests a Join from the Worker. The Worker issues a Client-specific participant ID and join token.
- Host and Client both connect via WebSocket to the same Durable Object and exchange SDP and ICE Candidates.
- WebRTC’s ICE first tries a direct connection using Host Candidates and Server Reflexive Candidates obtained via STUN.
- Only when a direct connection fails due to NAT or firewall conditions does traffic go through Cloudflare TURN.
- Once the DataChannel opens, WebRTC4UnrealNetDriver creates a UNetConnection per Client and starts the same Replication and RPC as a normal Listen Server.
Both are Listen Servers, so the connection topology is a star centered on the Host. Clients don’t connect to each other directly; each Client establishes a WebRTC connection with the Host. Also, when the Host quits, the game session ends.
Implementing with HeteroCloud + Cloudflare
As I wrote at the start, the essence of this is freedom from vendor lock-in. HeteroCloud is still in beta, and since it’s free, I can’t guarantee high quality at this point. But Cloudflare’s TURN, Durable Objects, and Workers cost money once you go beyond a certain range, free tier notwithstanding. So let’s take advantage of that freedom from vendor lock-in and build a Worker that runs on HeteroCloud Flow by default and migrates to Cloudflare on fallback!!!
https://github.com/mizuamedesu/WebRTC4Unreal/tree/main/Backend/Worker

This way, the Worker holds the credentials and delegates to Flow, falling back to the Cloudflare Durable Object + TURN setup only when that fails. As a result, normal operation only costs Worker requests, and when Flow goes down it falls back to Cloudflare — low-cost HA.
Of course, as it stands the Worker is a single point of failure, but if you make it a failover setup that also mixes in another cloud (AWS Lambda), you can eliminate that single point of failure.
Closing
WebRTC itself isn’t that difficult a technology, and now that LLMs let you implement things blazingly fast, I think the abstraction benefits you get from vendor lock-in are thinning out. You could also see it as having a wider range of options available, which makes decision-making all the more important.