Use DataChannels for application messages such as chat, sensor updates, and control events. Use media tracks for standard audio and video transport.
A publisher creates a named channel. Multiple subscribers can receive its messages, and one selected subscriber can reply on that publisher channel.
Your backend makes authenticated SFU API calls. Browser and native endpoints own the corresponding WebRTC channels. Refer to application architecture for the credential and signaling boundary.
To connect two endpoints and send a first message, follow the complete message recipe. Transport setup connects each endpoint to the SFU. Application-channel allocation selects the named messages to publish or receive.
Start with a publisher and subscriber whose DataChannel transports are already connected. Each has its own SFU session and PeerConnection. These steps add a channel without repeating transport setup.
- On your backend, call
POST /apps/{appId}/sessions/{publisherSessionId}/datachannels/new. In the request'sdataChannelsarray, add an entry withlocation: "local"and the chosendataChannelName. Check the publication result and retain the successful allocation before subscribing. - On the subscriber's session, call
POST /apps/{appId}/sessions/{subscriberSessionId}/datachannels/new. ItsdataChannelsentry useslocation: "remote", the publisher'ssessionId, and the samedataChannelName. Check the subscription result and retain its successful allocation. - Return each channel's
idto its own endpoint aschannelId. Create its browser channel withpc.createDataChannel(name, { negotiated: true, id: channelId }). Publisher and subscriber IDs can differ. This configuration uses reliable, ordered delivery by default. - Install the subscriber's message handler and wait for the channels to open. Have the subscriber report readiness through your application before the publisher sends a message. Confirm that the subscriber receives it.
To allocate several channels on a session, batch their entries in one create request. Keep that request all local publications or all remote subscriptions.
For other delivery settings, configure each endpoint's API allocation and browser channel as described next. Follow per-session mutation ordering throughout the operation. The Connection API supplies authentication and complete request schemas.
The publisher chooses one delivery policy for each named channel: ordered, maxRetransmits, and maxPacketLifeTime. Every subscriber must mirror that policy in its API request, and each endpoint configures its browser DataChannel with the same settings.
Channels in the same session or application can use different policies. Publish separate named channels for different policies, such as reliable commands and unreliable player-state. Asymmetric reliability between a publisher and its subscribers is outside the supported contract.
DataChannels use reliable, ordered delivery by default. Choose a policy for the messages on each channel:
| Payload | Example settings | Reason |
|---|---|---|
| Discrete commands or text | ordered: true, no retransmission limit |
Preserve the message sequence |
| Independent messages that all matter | ordered: false, no retransmission limit |
Permit reordering while retaining retransmission |
| Replaceable state or pointer movement | ordered: false, maxRetransmits: 0 |
Allow old updates to be lost |
The optional delivery fields are:
| Field | Meaning |
|---|---|
ordered |
Omitted means true. Set to false to allow delivery out of order. |
maxRetransmits |
Maximum retransmission attempts after the initial send. Set to zero for no retransmissions. |
maxPacketLifeTime |
Transport time budget in milliseconds for attempting transmission and retransmission of a message. |
Set at most one of maxRetransmits and maxPacketLifeTime. Omit both for reliable delivery. Omitting a retry limit is different from setting maxRetransmits: 0. Ordering and retry behavior are independent: unordered delivery can still be reliable.
maxPacketLifeTime is a transport budget, not an end-to-end message-age deadline. Use application timestamps or sequence numbers to discard stale updates. Reliable delivery does not provide durable storage or confirm command execution.
Your application supplies the publisher's policy to subscribers along with the publication's session ID and channel name. Configure each browser with that policy and its own allocated channel ID. Negotiated DataChannels do not communicate these settings to the browser automatically.
waitForAck and canReply are chosen separately for each subscription. They do not change the published channel's delivery policy.
For replaceable player state, your backend creates the publisher's allocation with unordered delivery and no retransmissions:
{
"dataChannels": [
{
"location": "local",
"dataChannelName": "player-state",
"ordered": false,
"maxRetransmits": 0
}
]
}After checking the result, return its channel ID to the publisher. In the publisher browser, publisherPc is its PeerConnection and publisherChannelId is its allocated ID:
const publisherChannel = publisherPc.createDataChannel("player-state", {
negotiated: true,
id: publisherChannelId,
ordered: false,
maxRetransmits: 0,
});On each subscriber's session, the backend requests the same publication with the same delivery policy:
{
"dataChannels": [
{
"location": "remote",
"sessionId": "<PUBLISHER_SESSION_ID>",
"dataChannelName": "player-state",
"ordered": false,
"maxRetransmits": 0
}
]
}After checking the subscription result, return its channel ID to that subscriber. In the subscriber browser, use its own subscriberPc and subscriberChannelId:
const subscriberChannel = subscriberPc.createDataChannel("player-state", {
negotiated: true,
id: subscriberChannelId,
ordered: false,
maxRetransmits: 0,
});Cloud gaming uses separate named channels for reliable keyboard events and unreliable pointer movement. Pocket Radio separates reliable device messages on robot from replaceable updates on spectrum.
Set waitForAck: true on a remote subscription to hold publisher-to-subscriber delivery until the subscriber signals readiness. This field applies only to location: "remote" and defaults to false.
This example subscribes to a device-events publication that uses reliable, ordered defaults. Your backend creates the remote subscription on the subscriber's session:
{
"dataChannels": [
{
"location": "remote",
"sessionId": "<PUBLISHER_SESSION_ID>",
"dataChannelName": "device-events",
"waitForAck": true
}
]
}When the negotiated channel opens, the subscriber sends a readiness message:
// channel is the negotiated remote DataChannel for this subscription.
if (channel.readyState === "open") {
channel.send("ready");
} else {
channel.addEventListener("open", () => channel.send("ready"), { once: true });
}The SFU consumes this first message and opens the gate. It must arrive within 30 seconds of creating the remote subscription. Otherwise the SFU tears down that gated channel. Create a new subscription to retry.
This acknowledgment establishes subscriber readiness. It is not an application command acknowledgment. Send a separate readiness message before commands, and use your own command IDs and acknowledgments when needed.
The gate is not a durable replay queue. Send the readiness acknowledgment before publisher traffic that must be delivered. Without canReply, subsequent subscriber messages are not forwarded to the publisher.
Set canReply: true when a subscriber needs to respond on the same publisher channel. The field applies only to location: "remote" and defaults to false.
Reply access has these properties:
- At most one subscriber can hold it for a publisher DataChannel.
- Granting it to another subscriber replaces the previous selection.
- Replies go to the publisher and are not forwarded to other subscribers.
Your backend must authenticate the operator and authorize the grant. canReply controls the SFU return path. Application state determines who may receive that permission.
This example uses the same reliable, ordered device-events policy. Your backend creates the subscription on the receiving session:
{
"dataChannels": [
{
"location": "remote",
"sessionId": "<PUBLISHER_SESSION_ID>",
"dataChannelName": "device-events",
"canReply": true
}
]
}After its negotiated channel opens, that subscriber can send messages to the publisher.
To grant or revoke access on an existing subscription, call PUT /apps/{appId}/sessions/{subscriberSessionId}/datachannels/update from your backend:
{
"dataChannels": [
{
"location": "remote",
"sessionId": "<PUBLISHER_SESSION_ID>",
"dataChannelName": "device-events",
"canReply": true
}
]
}Set canReply: false to revoke access. Update the new subscriber with canReply: true to transfer access. Check every result before changing the application's displayed controller state.
The existing negotiated channel carries replies after access is granted. Refer to Pocket Radio's operator ownership ↗ and cloud-gaming control ownership for application examples.
A subscription can set both waitForAck and canReply. Its first message opens the readiness gate and is consumed. Later messages reach the publisher while that subscriber holds reply access.
On the backend, send PUT /apps/{appId}/sessions/{sessionId}/datachannels/close with the IDs allocated on that endpoint's session:
{
"dataChannels": [{ "id": 2 }, { "id": 3 }]
}Match results by id. A close_track_error item means the channel is absent or already closed. Retain other failed or unreported IDs for retry. Refer to close-result handling.
Closure needs no SDP exchange. Close the endpoint's channel objects, then its PeerConnection when no longer needed. Follow teardown ordering if work is still outstanding.
DataChannels can carry encoded media as binary application messages. The SFU forwards these bytes without interpreting their media format. Publication and subscription, delivery settings, transport congestion control, and data statistics still apply.
Your application supplies media framing, compatible decoders, playback timing, synchronization, and recovery from missing or late frames. Pace writes and bound application buffers. SFU codec negotiation, simulcast, and WebSocket media adapters operate on media tracks. They do not interpret media embedded in DataChannel messages.
Run the DataChannel example ↗ to establish two endpoints, open the readiness gate, send a publisher message and subscriber reply, and compare unordered updates.
The example keeps the SFU App Secret on a local Node.js server. It is an experimental localhost exercise, with no public application authentication or room membership. Follow its API and lifecycle guide ↗ to inspect negotiation and teardown.