- Introduction
- WebRTC Primer
- Architecture Overview
- HTTP Signaling Protocol
- Identity Assertions
- WebRTC PeerConnection Configuration
- Connection Flow
- SDP Examples
- NetworkID
- Troubleshooting Client Connections
NetherNet is Minecraft's peer-to-peer networking transport layer built on WebRTC. It establishes direct data-channel connections between game clients and servers using standard WebRTC offer/answer negotiation.
This guide documents the HTTP signaling protocol that the Minecraft client uses to exchange WebRTC session descriptions with a server as well as the data channel format used on the connection. By implementing the protocol described here, partners can build a signaling inteface that bridges the Minecraft client to their server infrastructure.
This document covers:
- The HTTP signaling endpoint the client calls and the request/response format it expects.
- The WebRTC
PeerConnectionconfiguration the client uses, so the partner can create a compatible peer. - The end-to-end connection flow from offer creation through data-channel open.
If you are already familiar with WebRTC, you can skip to Section 3.
SDP is a text format that describes a peer's media and data-channel capabilities, supported codecs, network addresses, and security parameters. In WebRTC, two peers exchange SDP documents — an offer and an answer — to agree on connection parameters.
ICE is the process by which peers discover network paths to each other. The ICE agent gathers candidates — possible IP address and port combinations — and tests them for connectivity. Candidate types include:
| Type | Description |
|---|---|
| Host | A local address on the machine's network interface. |
| Server-reflexive | The public address as seen by a STUN server (NAT traversal). |
| Relay | An address allocated on a TURN relay server (fallback when direct paths fail). |
- Trickle ICE: candidates are sent to the remote peer incrementally as they are discovered, in parallel with the offer/answer exchange. This can reduce setup time.
- Full ICE: all candidates are gathered before the offer or answer is sent. The SDP contains every candidate the peer discovered.
NetherNet's HTTP signaling uses full ICE (trickle ICE disabled). The entire SDP — including all ICE candidates — is transmitted in a single HTTP request and response. This simplifies the signaling protocol to a single round-trip but means that candidate gathering must complete before the SDP is sent.
- STUN servers help a peer discover its public (server-reflexive) address.
- TURN servers act as relays when direct connectivity is not possible.
Note: We do not recommend configuring STUN or TURN servers when using HTTP signaling. Because trickle ICE is disabled, every configured STUN/TURN server must be contacted before the SDP can be sent, which adds latency to connection setup. If relay connectivity is required, consider alternative approaches.
WebRTC data channels provide bidirectional transport for arbitrary application data (as opposed to audio/video media). NetherNet uses data channels exclusively — no audio or video tracks will be requested by a legitimate Minecraft client.
┌──────────────┐ HTTPS ┌──────────────────────┐
│ │ ─────────────────────► │ │
│ Minecraft │ POST /v1/join/{id} │ Partner Signaling │
│ Client │ Body: SDP offer │ Server │
│ │ ◄───────────────────── │ │
│ │ 200 OK │ │
│ │ Body: SDP answer │ │
└──────┬───────┘ └──────────────────────┘
│
│ WebRTC P2P (DTLS + SCTP)
│
▼
┌──────────────┐
│ Partner │
│ Host │
│ (WebRTC) │
└──────────────┘
- The Minecraft client gathers a complete SDP offer (with all ICE candidates).
- The client sends the offer to the partner's signaling server via a single HTTP POST.
- The signaling server is responsible for getting the offer to the host and returning the host's SDP answer in the HTTP response.
- Once the client receives the answer, WebRTC ICE connectivity checks run directly between the client and host.
- When connectivity is established, SCTP data channels open and game traffic flows peer-to-peer.
The signaling server is only involved during the initial SDP exchange. After that, all traffic flows directly between the client and host over the WebRTC connection.
Before initiating a WebRTC connection, the client checks whether the server supports NetherNet connections:
GET {serverUrl}/v1/join
| Field | Value |
|---|---|
| Method | GET |
| URL | {serverUrl}/v1/join |
The server should return HTTP 2xx if it accepts NetherNet connections, or a non-2xx status (e.g., 404) if it does not. The response body is ignored by the client. If the server does not support this endpoint, the client will not attempt a WebRTC connection.
POST {serverUrl}/v1/join/{networkId}
| Component | Description |
|---|---|
{networkId} |
The client's NetworkID — an opaque string that uniquely identifies the client (see Section 9). |
| Field | Value |
|---|---|
| Method | POST |
| URL | {serverUrl}/v1/join/{networkId} |
| Content-Type | application/sdp |
| Body | Raw SDP offer text (UTF-8). Contains the complete offer including all ICE candidates. |
| Field | Value |
|---|---|
| Status | 2xx for success; any other status is treated as a failure. |
| Content-Type | application/sdp |
| Body | Raw SDP answer text (UTF-8). Must contain the complete answer including all ICE candidates. |
- The client sends exactly one HTTP request per connection attempt.
- On HTTP
2xx: the client parses the response body as an SDP answer and proceeds with WebRTC negotiation. - On any non-
2xxstatus: the client reports a signaling failure and the connection attempt ends. - The client does not retry on failure. A new connection attempt would produce a new HTTP request.
NetherNet uses a=identity attributes (defined in RFC 8827 §5) in both directions of the SDP exchange:
- In the offer, the client asserts the authenticated player's identity. The server validates this to decide whether to admit the player. See §5.1 Validating the Client Assertion in the Offer.
- In the answer, the server asserts a long-lived operator identity. The client uses this for a Trust On First Use (TOFU) check — pinning the operator's public key on the first connection and re-checking it on every subsequent connection. See §5.2 Producing the Server Assertion in the Answer.
Both assertions share the same on-the-wire envelope — a base64-encoded JSON object containing an idp block and an opaque assertion string. The internal assertion always carries a JWT plus a detached JWS over the SDP's a=fingerprint lines, signed with the private key corresponding to the cpk (public key) claim in the JWT. The two directions differ only in what the JWT is and how the verifier anchors trust in it.
The client embeds its assertion in the SDP offer body of POST /v1/join/{networkId}. The server should validate it before forwarding the offer or generating an answer.
The SDP offer will contain a session-level a=identity attribute. This attribute carries a base64-encoded JSON object with the following structure:
{
"idp": {
"domain": "<auth-service-domain>",
"protocol": "default"
},
"assertion": "<assertion string>"
}| Field | Description |
|---|---|
idp.domain |
The domain of the Minecraft auth service that issued the GameServerToken. |
idp.protocol |
Always "default". |
assertion |
A JSON string containing the token and signed fingerprints, defined below. |
The assertion field is a string containing:
{
"token": "<GameServerToken JWT>",
"fingerprints": "<detached JWS (compact serialization)>"
}| Field | Description |
|---|---|
token |
A GameServerToken JWT issued by the Minecraft auth service. Contains the player's identity claims (XUID, PlayFabId, UUID, permissions) and a cpk (client public key) claim. |
fingerprints |
A detached JWS (RFC 7515, Appendix F) in compact serialization: <base64url(header)>..<base64url(signature)>. The payload is omitted because it can be reconstructed from the SDP a=fingerprint lines. |
The a=identity attribute is a session-level attribute. It appears after the a=fingerprint lines and before the first m= line:
v=0
o=- 1181923068 1181923196 IN IP4 0.0.0.0
s=-
t=0 0
a=fingerprint:sha-256 4A:AD:B9:B1:3F:82:18:3B:54:02:12:DF:3E:5D:49:6B:19:E5:7C:AB
a=identity:eyJpZHAiOnsiZG9tYWluIjoiYXV0aC5taW5lY3JhZnQub3JnIiwicHJvdG9jb2wi...
m=application 9 UDP/DTLS/SCTP webrtc-datachannel
...
When the server receives an SDP offer, it should validate the identity assertion before proceeding with WebRTC negotiation:
-
Parse
a=identity. Extract the attribute value from the SDP. Base64-decode it and JSON-parse to obtain theidpandassertionfields. -
Extract token and signed fingerprints. Parse the
assertionstring as JSON to obtain thetokenandfingerprintsfields. -
Validate the
GameServerTokenJWT. Verify the JWT signature against the Minecraft auth service's public keys. This yields the player's identity claims, including thecpk(client public key). -
Reconstruct the fingerprint payload. Extract all
a=fingerprintattributes from the SDP and serialize them as canonical JSON (keys sorted lexicographically, no extra whitespace):{"fingerprint":[{"algorithm":"sha-256","digest":"4A:AD:B9:B1:3F:82:18:3B:54:02:12:DF:3E:5D:49:6B:19:E5:7C:AB"}]} -
Verify the detached JWS. Using the
cpkpublic key from the validated token:- Read the
algheader from the JWS (e.g.,ES384). - Reconstruct the JWS signing input:
base64url(header).base64url(canonical_fingerprint_json). - Verify the signature.
A valid signature proves that the authenticated player (the token holder) is the entity that generated the DTLS certificate whose fingerprint is in the SDP.
- Read the
-
Authorize the player. Use the identity claims from the token (XUID, UUID, permissions, etc.) to decide whether to allow the connection — for example, checking allowlists, banlists, or player limits.
-
Strip
a=identitybefore setting the remote description. WebRTC implementations will reject unknown SDP attributes. Remove thea=identityline from the SDP before passing it tosetRemoteDescription().
Because the fingerprints field uses a detached JWS (payload omitted), both the client and server must produce identical bytes when serializing the fingerprint structure. The canonical JSON rules are:
- Object keys are sorted lexicographically (by Unicode code point).
- No insignificant whitespace — no spaces after
:or,, no newlines or indentation. - No trailing commas.
- Strings use minimal escaping (only characters required by RFC 8259).
This is a subset of RFC 8785 (JSON Canonicalization Scheme).
The assertion creates a verified chain from the auth service to the DTLS transport:
Auth Service ──signs──► GameServerToken (contains cpk)
│
cpk private key signs
│
▼
a=fingerprint values (in SDP)
│
WebRTC enforces match with
│
▼
DTLS certificate (at handshake)
- MITM prevention: An attacker who modifies the
a=fingerprintvalues in the SDP cannot produce a valid signature without thecpkprivate key. An attacker who replays original signed fingerprints cannot complete the DTLS handshake without the corresponding DTLS private key. - Early rejection: Invalid or unauthorized players can be rejected at signaling time — before ICE/DTLS negotiation — saving resources.
- No post-DTLS verification needed: Because WebRTC enforces that the DTLS certificate matches the
a=fingerprintvalues, and the assertion proves the authenticated identity owns those fingerprints, no additional verification is needed after the DTLS handshake.
If the SDP offer does not contain an a=identity attribute, the server may either:
- Reject the connection if authentication is required.
- Allow the connection for open/unauthenticated scenarios.
This is a server policy decision.
The Minecraft client expects every SDP answer to contain a server-side a=identity attribute. The server must always include it, regardless of whether signaling is reached over HTTPS or plaintext HTTP. What changes with the transport is how the client uses it — specifically, whether the player ever sees a trust prompt.
The client picks a trust anchor based on how it reached the signaling endpoint:
- HTTPS signaling — TLS is the trust anchor. When the client reached
/v1/join/{networkId}over a TLS connection with a valid server certificate, the TLS handshake already authenticated the server endpoint and protected the SDP answer in transit. The client trusts the server on the strength of the certificate. Thea=identityattribute is still verified for structural correctness (signature checks on the JWT and the fingerprint JWS), but the client does does not show the player a first-use dialog. Returning players and new players are treated identically. - Plaintext HTTP signaling — Trust On First Use is the trust anchor. When the client reached signaling over unauthenticated HTTP (including by raw IP + port), the SDP — and its
a=fingerprintlines — cannot be trusted on the basis of the transport alone. The client falls back to TOFU: the first time it sees a given operator public key, it prompts the player to confirm; on every subsequent connection that presents the same key, the client accepts silently. The key — not the address or hostname — is the unit of trust. A partner running many servers behind one keypair is trusted as a partner once, and may move clients between servers (for example viaTransferPacket) without re-prompting.
In both cases, a missing or malformed a=identity will cause the client to refuse the connection. Producing a valid server assertion is therefore a required step for all partners, even those who operate exclusively over HTTPS — it is the mechanism that lets the client recognize the same operator across connections and that keeps the door open to clients reached over non-TLS channels.
The envelope is structurally identical to the client offer assertion. The server adds a session-level a=identity attribute to the answer SDP carrying a base64-encoded JSON object:
{
"idp": {
"domain": "<partner-domain>",
"protocol": "default"
},
"assertion": "<assertion string>"
}| Field | Description |
|---|---|
idp.domain |
A domain identifying the partner. The client does not validate this; it may be surfaced in the first-use prompt as untrusted display text. |
idp.protocol |
Always "default". |
assertion |
A JSON string containing the server's identity JWT and signed fingerprints, defined below. |
The assertion field is a JSON string containing:
{
"token": "<server identity JWT>",
"fingerprints": "<detached JWS (compact serialization)>"
}Unlike the client's GameServerToken, the server token is a standard JWT (RFC 7519) that the partner produces themselves. It is not issued by the Minecraft auth service and is not validated against any centralized issuer — trust is anchored either by TLS (HTTPS signaling) or by the client's pin store (plaintext HTTP signaling). The only NetherNet-imposed constraints are:
cpk(required). The operator's long-lived public key, serialized the same way as thecpkclaim inGameServerToken. This is the value that gets pinned by the client. The claim namecpkis reused so the same client-side verification code handles both kinds of token; semantically it is simply "the public key the JWT is bound to."algheader / signature. The JWT must be self-signed with the private key corresponding tocpk. The client verifies the JWT signature using the embeddedcpk.iat,exp(recommended). Standard timestamps. The token may be issued at server startup with a long-livedexp, or rotated periodically. The pin survives token rotation as long ascpkdoes not change. The client rejects clearly expired tokens.- Other claims (optional). Any other JWT claims (
iss,sub,aud, custom claims) are permitted. When TOFU is in effect (plaintext HTTP signaling, unknown key), human-readable claims such asissmay be surfaced in the first-use prompt as untrusted display text to help the player identify the partner. Under HTTPS signaling these claims are not shown to the player.
Key identity, not endpoint identity. Trust attaches to
cpk, not to hostname, IP, orNetworkID. To make a fleet of servers appear as one trust unit to clients, share a single keypair across them. Conversely, rotating the keypair will re-prompt every connecting player as if you were a new partner — treat key rotation as a deliberate, infrequent event.
The fingerprints field is a detached JWS (RFC 7515, Appendix F) in compact serialization (<base64url(header)>..<base64url(signature)>), constructed identically to the client side but over the answer's a=fingerprint lines:
- Collect every
a=fingerprintline from the SDP answer. - Serialize them as canonical JSON (see §5.1 Canonical JSON):
{"fingerprint":[{"algorithm":"sha-256","digest":"11:22:33:44:..."}]} - Build the JWS header, e.g.
{"alg":"ES384"}. The algorithm must match the keypair behindcpk. - Sign
base64url(header) + "." + base64url(canonical_fingerprint_json)with thecpkprivate key. - Emit the compact form with the payload omitted:
base64url(header) + ".." + base64url(signature).
The a=identity line is a session-level attribute. Place it after the a=fingerprint lines and before the first m= line — the same position the client uses in the offer:
v=0
o=- 987654321 2 IN IP4 127.0.0.1
s=-
t=0 0
a=fingerprint:sha-256 11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00
a=identity:eyJpZHAiOnsiZG9tYWluIjoicGFydG5lci5leGFtcGxlIiwicHJvdG9jb2wi...
m=application 9 UDP/DTLS/SCTP webrtc-datachannel
...
For reference, the client's verification flow is:
- Parse the
a=identityenvelope from the answer SDP. - Extract
tokenandfingerprintsfrom the opaque assertion. - Read the
cpkclaim from the JWT; reject the assertion ifcpkis missing or malformed. - Verify the JWT's self-signature using
cpk(proves possession of the corresponding private key). - Verify the fingerprints JWS using the same
cpk, with the payload reconstructed from the answer'sa=fingerprintlines as canonical JSON. - Check
expif present; reject expired tokens. - Trust anchor check — depends on how signaling was reached:
- HTTPS signaling → TLS already authenticated the server; accept without consulting the pin store and without prompting the player.
- Plaintext HTTP signaling → look up the digest of
cpkin the client's local pin store. Known → accept silently and continue. Unknown → prompt the player with the key fingerprint and connection target; on accept, store the pin and continue; on deny, abort the connection.
- Strip
a=identityfrom the SDP and pass the cleaned answer to WebRTC, which enforces that the DTLS certificate matches the now-validateda=fingerprintlines.
- Prefer HTTPS for signaling. When signaling is reached over TLS the client trusts the server based on the certificate and never shows the player a trust dialog. This is the smoothest experience for new players and removes the need for them to verify a key fingerprint out of band.
- Always emit
a=identity, even over HTTPS. The client requires the attribute on every answer. Under HTTPS it is verified structurally but does not drive a player prompt; under plaintext HTTP it is what TOFU pins. Omitting it will cause the client to refuse the connection on every transport. - Generate the keypair once. Treat the operator keypair as long-lived infrastructure. Storing it in a secrets manager and provisioning it to every host that signs answers is sufficient; there is no need for per-host or per-session keys.
- Share the keypair across your fleet. If you operate multiple servers (including for transfer scenarios), use the same keypair so any player who does go through TOFU sees a single first-use prompt for your entire fleet.
- Avoid unnecessary rotation. For clients on plaintext HTTP signaling, every key rotation forces a re-prompt for every returning player. Rotate only on suspected compromise or as part of a documented migration. (HTTPS clients are unaffected by rotation.)
- Publish your key fingerprint out of band if you support non-HTTPS signaling. TOFU players who want to verify the key on first use have no other way to do so. Publishing a sha-256 fingerprint of your
cpkon a page they already trust (your website, store listing, etc.) is the only mechanism that closes the first-use window. Partners who operate exclusively over HTTPS do not need to publish a fingerprint. - Token format is up to you. The JWT can be produced by any standard JWT library. The only requirements are the
cpkclaim and that the JWT be self-signed with the corresponding private key.
The following describes how the Minecraft client configures its WebRTC PeerConnection.
| Setting | Value | Notes |
|---|---|---|
| TCP Candidate Policy | disabled |
No TCP ICE candidates are generated. All connectivity uses UDP. |
| Bundle Policy | max-bundle |
All data channels are bundled over a single transport. |
| Trickle ICE | Disabled | All ICE candidates are gathered before the SDP is sent. |
| STUN/TURN Servers | None | No STUN or TURN server URLs are configured. Only host (local) ICE candidates are gathered. |
| Data Channels | 2 | The client opens two SCTP data channels: ReliableDataChannel and UnreliableDataChannel (see below). |
The client creates two SCTP data channels on the offering side:
| Channel Name | Label | Ordered | Reliable | maxRetransmits |
Fragmentation |
|---|---|---|---|---|---|
| Reliable | "ReliableDataChannel" |
Yes | Yes (default SCTP) | (default) | Yes |
| Unreliable | "UnreliableDataChannel" |
No | No | 0 |
No |
ReliableDataChannelis used for game data that must arrive in order and without loss. This channel supports NetherNet's fragmentation protocol (see Section 6.1) for messages that exceed the SCTP max message size.UnreliableDataChannelis used for data that is latency-sensitive and can tolerate loss (e.g., movement updates). Messages on this channel are not fragmented — any message larger than the SCTP max message size minus 1 byte (for the header) will be dropped.
The host (answering side) should expect to receive these two data channels via the ondatachannel event and should be prepared to send and receive on both.
All messages on both data channels are prefixed with a 1-byte header. The receiver must strip this header to recover the application payload.
The header byte is an unsigned integer that indicates the fragment position:
| Header Value | Meaning |
|---|---|
0 |
Complete message (unfragmented) or final fragment of a fragmented message. |
N (where N > 0) |
Fragment with N remaining fragments to follow. Fragments arrive in descending order: the first fragment has the highest header value, and the last fragment has header 0. |
Most messages fit within a single SCTP message. In this case, the data on the wire looks like:
┌────────┬──────────────────────┐
│ Header │ Payload │
│ 0x00 │ (application data) │
└────────┴──────────────────────┘
1 byte N bytes
The receiver strips the 1-byte header (which is 0x00) and delivers the remaining bytes as the complete message.
When a message exceeds the negotiated SCTP max message size, the sender splits it into multiple fragments, each with its own 1-byte header. Fragments are sent in payload order, but the header byte acts as a countdown — it indicates how many fragments still remain after this one. The first fragment on the wire carries the highest header value (the total fragment count minus 1), and the final fragment carries header 0.
Example: a message split into 3 fragments:
Fragment 1: [0x02] [payload chunk 1] ← 2 fragments still to come
Fragment 2: [0x01] [payload chunk 2] ← 1 fragment still to come
Fragment 3: [0x00] [payload chunk 3] ← final fragment
The receiver accumulates payload data from each fragment (stripping the header byte) until it receives a fragment with header 0, at which point the accumulated data is delivered as a single complete message.
Important: Fragmentation is only supported on the
ReliableDataChannel. TheUnreliableDataChanneldoes not support reassembly — the header byte is still present but must always be0. Messages that would require fragmentation are dropped by the sender on the unreliable channel.
sequenceDiagram
participant C as Minecraft Client
participant S as Partner Signaling Server
participant H as Partner Host (WebRTC)
Note over C: Create PeerConnection
Note over C: Create data channels:<br/>ReliableDataChannel<br/>UnreliableDataChannel
C->>C: createOffer()
C->>C: setLocalDescription(offer)
Note over C: ICE gathering begins...
Note over C: ICE gathering complete<br/>(all candidates in SDP)
C->>S: POST /v1/join/{clientNetworkId}<br/>Content-Type: application/sdp<br/>Body: SDP offer
S->>H: Forward SDP offer
Note over H: Create PeerConnection
H->>H: setRemoteDescription(offer)
H->>H: createAnswer()
H->>H: setLocalDescription(answer)
Note over H: ICE gathering begins...
Note over H: ICE gathering complete<br/>(all candidates in SDP)
H->>S: Return SDP answer
S->>C: HTTP 200 OK<br/>Content-Type: application/sdp<br/>Body: SDP answer
C->>C: setRemoteDescription(answer)
Note over C,H: ICE connectivity checks (direct P2P)
Note over C,H: DTLS handshake
Note over C,H: SCTP association established
Note over C,H: Data channels open — game traffic flows
- Client creates PeerConnection with the configuration described in Section 6.
- Client creates two data channels:
ReliableDataChannelandUnreliableDataChannel. - Client calls
createOffer()to generate an SDP offer. - Client sets the local description (the offer). This starts ICE candidate gathering.
- ICE gathering completes. Because trickle ICE is disabled, the client waits until all candidates have been gathered before proceeding.
- Client sends the SDP offer via
POST /v1/join/{clientNetworkId}to the partner's signaling server. - Signaling server forwards the offer to the partner's host.
- Host creates a PeerConnection and sets the received offer as its remote description.
- Host calls
createAnswer()to generate an SDP answer. - Host sets the local description (the answer) and gathers ICE candidates.
- ICE gathering completes on the host side.
Important: The host's SDP answer must include at least one ICE candidate that is reachable by the connecting client. If the host is behind a NAT, its local (host) candidates will use private addresses that remote clients cannot reach. In this case, the partner must ensure a server-reflexive candidate (or another publicly routable candidate) is present in the answer. This can be achieved by configuring a STUN server on the host's
PeerConnectionso that asrflxcandidate is gathered, or by programmatically injecting a server-reflexive candidate line (e.g.,a=candidate:... typ srflx ...) into the SDP answer before returning it.
- Host returns the SDP answer to the signaling server.
- Signaling server responds to the client's HTTP request with
200 OKand the SDP answer body. - Client sets the remote description (the answer).
- ICE connectivity checks run directly between client and host.
- DTLS handshake secures the connection.
- SCTP association is established and data channels open.
- Game traffic flows over the
ReliableDataChannelandUnreliableDataChannel.
Below is a representative SDP offer as the Minecraft client would produce. Actual values will vary per session.
v=0
o=- 123456789 2 IN IP4 127.0.0.1
s=-
t=0 0
a=group:BUNDLE 0
a=extmap-allow-mixed
a=msid-semantic: WMS
m=application 9 UDP/DTLS/SCTP webrtc-datachannel
c=IN IP4 0.0.0.0
a=ice-ufrag:abcd
a=ice-pwd:abcdefghijklmnopqrstuvwx
a=ice-options:trickle
a=fingerprint:sha-256 AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99
a=setup:actpass
a=mid:0
a=sctp-port:5000
a=max-message-size:262144
a=candidate:1 1 udp 2130706431 192.168.1.100 12345 typ host
a=candidate:2 1 udp 1694498815 203.0.113.50 54321 typ srflx raddr 192.168.1.100 rport 12345
a=end-of-candidates
| Line | Description |
|---|---|
a=group:BUNDLE 0 |
All data channels are bundled over a single transport (mid 0). |
m=application 9 UDP/DTLS/SCTP webrtc-datachannel |
Declares a data-channel-only application using DTLS over UDP with SCTP. |
a=ice-ufrag / a=ice-pwd |
ICE credentials for this session. The remote peer uses these to authenticate STUN binding requests. |
a=fingerprint:sha-256 ... |
DTLS certificate fingerprint. Used to verify the DTLS handshake. |
a=setup:actpass |
The offerer can act as either DTLS client or server. The answerer should choose active or passive. |
a=sctp-port:5000 |
The SCTP port number used for data channels. |
a=max-message-size:262144 |
Maximum SCTP message size (256 KB). |
a=candidate:... |
ICE candidates. With full ICE, all candidates are present in the SDP. The typ field indicates the candidate type (host, srflx, relay). |
a=end-of-candidates |
Signals that ICE gathering is complete. |
The answer mirrors the offer structure but with the host's own ICE credentials, fingerprint, candidates, and a setup value of active (assuming the offerer used actpass).
v=0
o=- 987654321 2 IN IP4 127.0.0.1
s=-
t=0 0
a=group:BUNDLE 0
a=extmap-allow-mixed
a=msid-semantic: WMS
m=application 9 UDP/DTLS/SCTP webrtc-datachannel
c=IN IP4 0.0.0.0
a=ice-ufrag:wxyz
a=ice-pwd:zyxwvutsrqponmlkjihgfedcb
a=ice-options:trickle
a=fingerprint:sha-256 11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00
a=identity:eyJpZHAiOnsiZG9tYWluIjoicGFydG5lci5leGFtcGxlIiwicHJvdG9jb2wi...
a=setup:active
a=mid:0
a=sctp-port:5000
a=max-message-size:262144
a=candidate:1 1 udp 2130706431 10.0.0.50 23456 typ host
a=end-of-candidates
A NetworkID identifies a peer in the NetherNet transport layer. It should be treated as an opaque string that is unique to each client. Do not make assumptions about its format (e.g., length, character set, or numeric range), as the format may change in future versions (for example, to a GUID or platform-specific identifier).
Current format: The
NetworkIDis currently a 64-bit unsigned integer rendered as a decimal string (e.g.,"9876543210123456789"). This is provided for illustrative purposes only — partners should not depend on this representation.
The client includes its own NetworkID in the URL path of the signaling request:
POST /v1/join/{networkId}
The signaling server can use this value to identify which client is connecting, route the offer to the appropriate host, and return the answer.