Realtime - Control Realtime SFU DataChannel delivery
- Announced
- Tags
- Realtime
- Severity
- info, set by
reef/rss-rule-v1 - Source
- https://developers.cloudflare.com/changelog/post/2026-08-13-datachannels-reliability-ordering/
<p><a href="https://developers.cloudflare.com/realtime/sfu/">Cloudflare Realtime SFU</a> is a <a href="https://developers.cloudflare.com/realtime/sfu/concepts/architecture/">WebRTC selective forwarding unit</a> that runs on Cloudflare's global network. It forwards audio, video, and application data between WebRTC clients without requiring you to manage SFU infrastructure or regions.</p> <p><a href="https://developers.cloudflare.com/realtime/sfu/features/datachannels/">DataChannels</a> are WebRTC channels for application messages. A client publishes a named DataChannel to Realtime SFU, and the SFU forwards its messages to every client that subscribes to that channel. Use DataChannels for low-latency payloads such as chat messages, game state, sensor updates, and control events.</p> <div tabindex="-1" class="heading-wrapper level-h4"><h4 id="what-changed">What changed</h4><a class="anchor-link" href="#what-changed"><span aria-hidden="true" class="anchor-icon"><svg width="16" height="16" viewBox="0 0 24 24"><path fill="currentcolor" d="m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 0 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 1 0-1.42-1.42Zm8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 0 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 1 0 1.42 1.42l3.88-3.89a4.49 4.49 0 0 0 0-6.33ZM8.83 15.17a1 1 0 0 0 1.1.22 1 1 0 0 0 .32-.22l4.92-4.92a1 1 0 0 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42Z"></path></svg></span></a></div> <p>Realtime SFU DataChannels now support unordered and partially reliable delivery. DataChannels remain reliable and ordered by default, so existing channels keep their current behavior.</p> <p>With ordered delivery, a delayed message can block later messages. For game state or sensor updates, recent data may be more useful than recovering an older message. Unordered delivery lets later messages proceed, while partial reliability limits retransmission attempts or the transport's retry window.</p> <div tabindex="-1" class="heading-wrapper level-h4"><h4 id="choose-delivery-behavior">Choose delivery behavior</h4><a class="anchor-link" href="#choose-delivery-behavior"><span aria-hidden="true" class="anchor-icon"><svg width="16" height="16" viewBox="0 0 24 24"><path fill="currentcolor" d="m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 0 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 1 0-1.42-1.42Zm8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 0 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 1 0 1.42 1.42l3.88-3.89a4.49 4.49 0 0 0 0-6.33ZM8.83 15.17a1 1 0 0 0 1.1.22 1 1 0 0 0 .32-.22l4.92-4.92a1 1 0 0 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42Z"></path></svg></span></a></div> <p>Delivery settings answer two questions: whether newer messages can bypass a delayed message, and when the transport should stop retrying delivery.</p> <p>The publisher chooses one policy for each named channel. Every subscriber mirrors it. Use separate named channels for different policies, such as reliable commands and unreliable pointer updates:</p> <div class="table-scroll" tabindex="0" role="region" aria-label="Table"><table> <thead> <tr> <th>Goal</th> <th>Settings</th> <th>Use when</th> </tr> </thead> <tbody> <tr> <td>Reliable, ordered delivery (default)</td> <td>Omit <code>ordered</code>, <code>maxRetransmits</code>, and <code>maxPacketLifeTime</code></td> <td>Messages remain useful and must arrive in order</td> </tr> <tr> <td>Reliable, unordered delivery</td> <td>Set <code>ordered: false</code>; omit both retry fields</td> <td>Messages remain useful, but later messages should not wait for earlier messages</td> </tr> <tr> <td>No retries or ordering</td> <td>Set <code>ordered: false</code> and <code>maxRetransmits: 0</code></td> <td>The application tolerates message loss and discards out-of-date updates</td> </tr> <tr> <td>Limited retries</td> <td>Set <code>maxRetransmits: <COUNT></code></td> <td>Brief recovery is useful, but repeated retries are not</td> </tr> <tr> <td>Time-limited transport retries</td> <td>Set <code>maxPacketLifeTime: <MILLISECONDS></code></td> <td>Limit how long the transport attempts transmission and retransmission</td> </tr> </tbody> </table></div> <p>Omitted <code>ordered</code> means <code>true</code>; ordering is independent of retries. Set at most one of <code>maxRetransmits</code> and <code>maxPacketLifeTime</code>. Omit both for reliable delivery, whether ordered or unordered. Setting <code>maxRetransmits: 0</code> explicitly requests no retransmissions.</p> <p><code>maxPacketLifeTime</code> does not impose 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.</p> <div tabindex="-1" class="heading-wrapper level-h4"><h4 id="apply-the-policy-end-to-end">Apply the policy end to end</h4><a class="anchor-link" href="#apply-the-policy-end-to-end"><span aria-hidden="true" class="anchor-icon"><svg width="16" height="16" viewBox="0 0 24 24"><path fill="currentcolor" d="m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 0 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 1 0-1.42-1.42Zm8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 0 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 1 0 1.42 1.42l3.88-3.89a4.49 4.49 0 0 0 0-6.33ZM8.83 15.17a1 1 0 0 0 1.1.22 1 1 0 0 0 .32-.22l4.92-4.92a1 1 0 0 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42Z"></path></svg></span></a></div> <p>Your application must supply the publisher's policy in every subscriber request and browser <code>createDataChannel()</code> call. Negotiated DataChannels do not communicate these settings to the browser automatically. Each endpoint uses its own allocated channel ID; <code>waitForAck</code> and <code>canReply</code> remain subscription-specific.</p> <p>The shared policy applies to one named publication, so other channels in the same session or application can use different policies. Asymmetric reliability is outside the supported contract.</p> <p>The following example configures unordered delivery with no retransmissions. It begins after you <a href="https://developers.cloudflare.com/realtime/sfu/features/datachannels/#set-up-a-datachannel">establish a DataChannel transport on both sessions and complete any required SDP exchange</a>. Run the API requests from your backend with <code>APP_ID</code>, <code>APP_TOKEN</code>, <code>PUBLISHER_SESSION_ID</code>, and <code>SUBSCRIBER_SESSION_ID</code> set in your environment.</p> <div data-nb-steps class="steps" data-astro-cid-sfejetki><ol> <li>On the publisher session, create the local DataChannel:</li> </ol><figure class="nb-code-figure" data-nb-lang="bash"><pre class="astro-code astro-code-themes github-light github-dark nb-shiki-c6xiwz" tabindex="0" data-language="bash" data-nb-lang="bash"><code><span class="line"><span class="nb-shiki-1t8gfj">curl</span><span class="nb-shiki-dzsirb"> --request</span><span class="nb-shiki-mdbnqw"> POST</span><span class="nb-shiki-dzsirb"> \</span></span> <span class="line"><span class="nb-shiki-dzsirb"> --url</span><span class="nb-shiki-mdbnqw"> "https://rtc.live.cloudflare.com/v1/apps/</span><span class="nb-shiki-140thh">$APP_ID</span><span class="nb-shiki-mdbnqw">/sessions/</span><span class="nb-shiki-140thh">$PUBLISHER_SESSION_ID</span><span class="nb-shiki-mdbnqw">/datachannels/new"</span><span class="nb-shiki-dzsirb"> \</span></span> <span class="line"><span class="nb-shiki-dzsirb"> --header</span><span class="nb-shiki-mdbnqw"> "Authorization: Bearer </span><span class="nb-shiki-140thh">$APP_TOKEN</span><span class="nb-shiki-mdbnqw">"</span><span class="nb-shiki-dzsirb"> \</span></span> <span class="line"><span class="nb-shiki-dzsirb"> --header</span><span class="nb-shiki-mdbnqw"> "Content-Type: application/json"</span><span class="nb-shiki-dzsirb"> \</span></span> <span class="line"><span class="nb-shiki-dzsirb"> --data</span><span class="nb-shiki-mdbnqw"> @-</span><span class="nb-shiki-1itgoe"> <<</span><span class="nb-shiki-mdbnqw">EOF</span></span> <span class="line"><span class="nb-shiki-mdbnqw">{</span></span> <span class="line"><span class="nb-shiki-mdbnqw"> "dataChannels": [</span></span> <span class="line"><span class="nb-shiki-mdbnqw"> {</span></span> <span class="line"><span class="nb-shiki-mdbnqw"> "location": "local",</span></span> <span class="line"><span class="nb-shiki-mdbnqw"> "dataChannelName": "player-state",</span></span> <span class="line"><span class="nb-shiki-mdbnqw"> "ordered": false,</span></span> <span class="line"><span class="nb-shiki-mdbnqw"> "maxRetransmits": 0</span></span> <span class="line"><span class="nb-shiki-mdbnqw"> }</span></span> <span class="line"><span class="nb-shiki-mdbnqw"> ]</span></span> <span class="line"><span class="nb-shiki-mdbnqw">}</span></span> <span class="line"><span class="nb-shiki-mdbnqw">EOF</span></span></code></pre></figure><ol start="2"> <li>On each subscriber session, pull the remote DataChannel with the same delivery settings:</li> </ol><figure class="nb-code-figure" data-nb-lang="bash"><pre class="astro-code astro-code-themes github-light github-dark nb-shiki-c6xiwz" tabindex="0" data-language="bash" data-nb-lang="bash"><code><span class="line"><span class="nb-shiki-1t8gfj">curl</span><span class="nb-shiki-dzsirb"> --request</span><span class="nb-shiki-mdbnqw"> POST</span><span class="nb-shiki-dzsirb"> \</span></span> <span class="line"><span class="nb-shiki-dzsirb"> --url</span><span class="nb-shiki-mdbnqw"> "https://rtc.live.cloudflare.com/v1/apps/</span><span class="nb-shiki-140thh">$APP_ID</span><span class="nb-shiki-mdbnqw">/sessions/</span><span class="nb-shiki-140thh">$SUBSCRIBER_SESSION_ID</span><span class="nb-shiki-mdbnqw">/datachannels/new"</span><span class="nb-shiki-dzsirb"> \</span></span> <span class="line"><span class="nb-shiki-dzsirb"> --header</span><span class="nb-shiki-mdbnqw"> "Authorization: Bearer </span><span class="nb-shiki-140thh">$APP_TOKEN</span><span class="nb-shiki-mdbnqw">"</span><span class="nb-shiki-dzsirb"> \</span></span> <span class="line"><span class="nb-shiki-dzsirb"> --header</span><span class="nb-shiki-mdbnqw"> "Content-Type: application/json"</span><span class="nb-shiki-dzsirb"> \</span></span> <span class="line"><span class="nb-shiki-dzsirb"> --data</span><span class="nb-shiki-mdbnqw"> @-</span><span class="nb-shiki-1itgoe"> <<</span><span class="nb-shiki-mdbnqw">EOF</span></span> <span class="line"><span class="nb-shiki-mdbnqw">{</span></span> <span class="line"><span class="nb-shiki-mdbnqw"> "dataChannels": [</span></span> <span class="line"><span class="nb-shiki-mdbnqw"> {</span></span> <span class="line"><span class="nb-shiki-mdbnqw"> "location": "remote",</span></span> <span class="line"><span class="nb-shiki-mdbnqw"> "sessionId": "</span><span class="nb-shiki-140thh">$PUBLISHER_SESSION_ID</span><span class="nb-shiki-mdbnqw">",</span></span> <span class="line"><span class="nb-shiki-mdbnqw"> "dataChannelName": "player-state",</span></span> <span class="line"><span class="nb-shiki-mdbnqw"> "ordered": false,</span></span> <span class="line"><span class="nb-shiki-mdbnqw"> "maxRetransmits": 0</span></span> <span class="line"><span class="nb-shiki-mdbnqw"> }</span></span> <span class="line"><span class="nb-shiki-mdbnqw"> ]</span></span> <span class="line"><span class="nb-shiki-mdbnqw">}</span></span> <span class="line"><span class="nb-shiki-mdbnqw">EOF</span></span></code></pre></figure><ol start="3"> <li>In the publisher and subscriber clients, create the negotiated browser DataChannel with the same settings. In this example, <code>pc</code> is the active <code>RTCPeerConnection</code>, and <code>channelId</code> is the ID returned by the corresponding API request:</li> </ol><figure class="nb-code-figure" data-nb-lang="ts"><pre class="astro-code astro-code-themes github-light github-dark nb-shiki-c6xiwz" tabindex="0" data-language="ts" data-nb-lang="ts"><code><span class="line"><span class="nb-shiki-1itgoe">const</span><span class="nb-shiki-dzsirb"> channel</span><span class="nb-shiki-1itgoe"> =</span><span class="nb-shiki-140thh"> pc.</span><span class="nb-shiki-1t8gfj">createDataChannel</span><span class="nb-shiki-140thh">(</span><span class="nb-shiki-mdbnqw">"player-state"</span><span class="nb-shiki-140thh">, {</span></span> <span class="line"><span class="nb-shiki-140thh"> negotiated: </span><span class="nb-shiki-dzsirb">true</span><span class="nb-shiki-140thh">,</span></span> <span class="line"><span class="nb-shiki-140thh"> id: channelId,</span></span> <span class="line"><span class="nb-shiki-140thh"> ordered: </span><span class="nb-shiki-dzsirb">false</span><span class="nb-shiki-140thh">,</span></span> <span class="line"><span class="nb-shiki-140thh"> maxRetransmits: </span><span class="nb-shiki-dzsirb">0</span><span class="nb-shiki-140thh">,</span></span> <span class="line"><span class="nb-shiki-140thh">});</span></span></code></pre></figure></div><script type="module" src="https://developers.cloudflare.com/home/runner/work/cloudflare-docs/cloudflare-docs/src/components/ui/steps/Steps.astro?astro&type=script&index=0&lang.ts"></script> <div tabindex="-1" class="heading-wrapper level-h4"><h4 id="related-documentation">Related documentation</h4><a class="anchor-link" href="#related-documentation"><span aria-hidden="true" class="anchor-icon"><svg width="16" height="16" viewBox="0 0 24 24"><path fill="currentcolor" d="m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 0 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 1 0-1.42-1.42Zm8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 0 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 1 0 1.42 1.42l3.88-3.89a4.49 4.49 0 0 0 0-6.33ZM8.83 15.17a1 1 0 0 0 1.1.22 1 1 0 0 0 .32-.22l4.92-4.92a1 1 0 0 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42Z"></path></svg></span></a></div> <ul> <li><a href="https://developers.cloudflare.com/realtime/sfu/">Realtime SFU overview</a></li> <li><a href="https://developers.cloudflare.com/realtime/sfu/features/datachannels/">DataChannels</a></li> <li><a href="https://developers.cloudflare.com/realtime/sfu/api/">Connection API</a></li> </ul>
The text above is the source's. reef added the severity, the dates it could read from the text, and the endpoints and scopes named in code.