Reading a friend’s Minecraft world over NetherNet: Xbox signaling, PmsgId and ICE candidates

2026-09-24

There are two very different ways a Minecraft Bedrock client reaches a world over NetherNet. A public Dedicated Server runs its own HTTP signaling endpoint you can POST an offer to directly (the path iceFetch uses for servers). A friend’s world — one someone opened from the game to their friends — has no public endpoint at all; it is reached through a cloud signaling service run by Microsoft and brokered by your Xbox relationship. This article documents that friend path end to end: how a world is discovered, how the service addresses it, and how the ICE candidates come back.

This is the signaling layer only — the SDP/candidate negotiation. It is how a diagnostic tool reads the connection info a friend’s world advertises to you; it does not complete a WebRTC session, and the identity step scopes it to worlds your own account is already a friend of.

1. Discovery — find the world and its ids

A world a friend has open is published on Xbox as a session-directory activity handle. Query the handles for the people in your own social graph:

POST https://sessiondirectory.xboxlive.com/handles/query?include=relatedInfo,customProperties
{ "type": "activity",
  "scid": "4fc10100-5f7a-4470-899b-280835760c07",
  "owners": { "people": { "moniker": "people", "monikerXuid": "<your xuid>" } } }

Each result’s customProperties.SupportedConnections[0] carries the two ids that matter:

"SupportedConnections": [ {
  "ConnectionType": 7,
  "NetherNetId": 3444785886168534373,               // the world's WebRTC network id (uint64)
  "PmsgId": "1f5d5f53-29ca-46a6-813d-6a4560bc5227"  // its signaling address (a GUID)
} ]
NetherNetId is a 64-bit integer — read it from the raw JSON text, not a parsed number, or JavaScript rounds it past 253. This step is read-only: no session join, no realtime (RTA) connection, and a plain user XSTS token is enough.

2. Authenticate to the signaling service

The signaling WebSocket is authorized with an MCToken (the franchise “authorization header”). You obtain it with the usual Bedrock chain: Microsoft account → Xbox user token → XSTS → PlayFab LoginWithXbox → franchise session/start. The same account also mints the a=identity assertion your offer needs (see §6).

3. Two protocols — and why the numeric id fails

The service, signal.franchise.minecraft-services.net, speaks two protocols on the same host:

legacyJSON-RPC
Path/ws/v1.0/signaling/{id}/ws/v1.0/messaging/connect
Addresses a peer bynumeric NetherNetIdPmsgId (a GUID)
Used byolder / LAN-style pathslive game-client friend worlds

This distinction is the whole game. Dialing a friend world’s numeric NetherNetId on the legacy endpoint returns { "Code": 1, "Message": "Player not found." } — a generic “that address isn’t registered” — because current game-client hosts sit on the JSON-RPC endpoint and are addressed by their PmsgId, not the numeric id. Pick the wrong protocol and every dial silently fails.

4. The JSON-RPC exchange

Connect with the MCToken and two correlation headers, request TURN credentials, then send the offer. The destination goes in toPlayerId; the inner message is itself a stringified JSON that carries your own network id and the signal:

// WebSocket: wss://signal.franchise.minecraft-services.net/ws/v1.0/messaging/connect
//   headers: Authorization: MCToken <token> ; session-id: <uuid> ; request-id: <uuid>

// 1) TURN credentials
{ "jsonrpc":"2.0", "id":"<uuid>", "method":"Signaling_TurnAuth_v1_0", "params":{} }

// 2) the offer, addressed to the host's PmsgId
{ "jsonrpc":"2.0", "id":"<uuid>", "method":"Signaling_SendClientMessage_v1_0",
  "params": {
    "toPlayerId": "<host PmsgId>",
    "messageId":  "<uuid>",
    "message": stringify({                 // a JSON string, not an object
      "jsonrpc":"2.0", "method":"Signaling_WebRtc_v1_0",
      "params": { "netherNetId":"<your id>",
                  "message":"CONNECTREQUEST <connId> <sdp offer>" } }) } }

Replies arrive as Signaling_ReceiveMessage_v1_0 notifications. You must acknowledge each with { "id": <its id>, "result": null } or the server cancels delivery. Unwrap the nested message to read the signal:

5. Trickle: send candidates to get candidates

A host only trickles its own candidates after it has received a few from the dialer. So once CONNECTRESPONSE arrives, send about three CANDIDATEADD messages — any plausible host candidates cross the threshold — and the host replies with its real ones, which you collect.

This is why the collected list is usually longer than the answer SDP: the answer embeds only the host candidates ready at once, while srflx (a STUN-reflexive public address) and relay (TURN) candidates take a round-trip to gather and arrive afterward:

candidate:… udp … 192.168.0.11 56117 typ host
candidate:… udp … 2001:…:e2a5 56117 typ host
candidate:… udp … 58.188.134.137 56117 typ srflx raddr 192.168.0.11 rport 56117
candidate:… udp … 20.202.45.254 54115 typ relay

6. Identity — and what you do not need

The offer must carry an a=identity attribute: a signed assertion (a franchise multiplayer token bound to a P-384 key) that proves which account is dialing, so the host can authorize a friend and reject a stranger. Notably, you do not need to join the world’s Xbox session (MPSD membership), open a realtime (RTA) connection, or publish your own activity to read the candidates — a JSON-RPC dial to the host’s PmsgId with a valid identity is enough. (For reference, the legacy endpoint’s envelope uses numeric type codes: 0 Error, 1 Signal, 2 Credentials, 3 Accepted, 4 Delivered.)

Reading only, and a note on privacy

Signaling yields the endpoints a host advertises; it does not itself open a game connection — completing one needs a full WebRTC/DTLS stack and UDP. A candidate list can include the host’s public IP (the srflx line). That is information the host already offers to the friends it authorizes to connect, and the identity assertion scopes it to your own account’s friends — but it is exactly why a feature like this should be opt-in and read-only, and why a candidate list is worth treating as sensitive.

Wrap-up

The friend path is: discover the world’s PmsgId via Xbox handles, authenticate to the franchise signaling service, dial that PmsgId over the JSON-RPC endpoint with an identity-signed offer, prime a few candidates, and collect the host’s trickled ICE candidates. The single easiest mistake is dialing the numeric NetherNetId on the legacy endpoint — live worlds are on JSON-RPC, keyed by PmsgId. You can run the whole flow against your own friends’ open worlds in iceFetch → Friends’ worlds, and see the server-side (HTTP) equivalent in How iceFetch works.

ToolLearnArticlesPrivacyTerms · iceFetch is not affiliated with Mojang or Microsoft.