Replay
This page explains how a connection gets recent messages that it missed, how to sign replay, and the limits of replay.
Before you start: Read Channels and Segments and Reconnection and Recovery.
Replay sends recent messages of a segment to a connection when the connection joins that segment. Use it to fill short gaps, for example after a reconnect. Replay is recent, bounded history. It is not a durable log.
1. Know what the server keeps
For each segment, the server keeps at most the last 100 messages, for at most 2 minutes. It keeps a message also when no connection is subscribed to the segment. Thus, a message from a write-only publisher is available to a later reader.
A message leaves the history when 100 newer messages arrive, or after 2 minutes. History can also become unavailable after a service event. Replay cannot guarantee that every missed message is available.
| Replay gives | Replay does not give |
|---|---|
| Recent messages of a segment | A durable log or a full history |
| The original message IDs and timestamps | A flag that marks a replayed message |
| Messages that the outage missed, inside the limits above | A marker at the end of the replay |
| A guarantee that no message is missing |
2. Sign the replay claim
Your server decides how much history each connection gets. The replay claim in the signed credentials controls it. The client cannot change it.
replay claim | History that the connection gets |
|---|---|
false (the default) | No history. Only messages published after the join. |
true | All the messages that the server keeps for the segment. |
{ lookbackMs: N } | The kept messages that the server got in the last N milliseconds. |
{ lookbackMs: 0 } | No history, the same as false. |
The server measures the window back from the moment of each join. It uses the time when it got each message, not the timestamp of the message. A window longer than 2 minutes gives no more history than true.
// The connection gets the messages of the last 30 seconds when it joins a segment.
const credentials = signer.sign({
channels: { kind: "restricted", references: ["room-42"] },
permissions: {
kind: "restricted",
segments: [{ segmentId: "chat", read: true, write: true }],
},
reference: user.realtimeReference,
replay: { lookbackMs: 30_000 },
});
3. Know when replay occurs
Replay occurs each time the connection joins a segment:
- When the connection opens, it joins
default. The server replaysdefault. - When you call
subscribe()for a named segment, the server replays that segment. - After a reconnect, the SDK opens a new connection. The server replays
defaultand each segment that the SDK subscribes to again. - When you cancel a subscription and subscribe again, the server replays the segment again.
A second subscribe() while the connection is a member of the segment does not replay again. A publish does not start a replay. Presence events are not replayed.
Replay needs read access to the segment. A connection without read access gets no replay and no live messages from that segment. See Permissions.
4. Use the lookback that the SDK suggests
On the first connection, the SDK suggests no lookback. When you want history on the first connection, sign replay: true or a fixed window.
After a reconnect, the SDK suggests a lookback for each attempt. The suggestion is the duration of the outage, rounded up to milliseconds, plus a five-second overlap. A failed attempt does not move the start of the outage. Thus, each later attempt suggests a larger window.
| SDK | Suggested lookback in the credential request |
|---|---|
| JavaScript | replayLookbackMs (number) |
| Python | replay_lookback_ms (int) |
| Go | ReplayLookback (time.Duration) |
| Java | replayLookback (Optional<Duration>) |
A suggestion is not permission. The SDK does not apply it. Your credential provider sends it to your endpoint, and your server decides:
// Browser: send the suggestion with the credential request.
const client = createClient({
credentialProvider: async (request) => {
const response = await fetch("/api/realtime-credentials", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
channelReference: request.channelReference,
replayLookbackMs: request.replayLookbackMs,
}),
signal: request.signal,
});
if (!response.ok) throw new Error("Credentials: " + response.status);
return response.json();
},
});
// Server: treat the suggestion as untrusted input. Cap it with your policy.
// userCanSeeHistory is your own authorization check for this user and channel.
const requested = body.replayLookbackMs;
const replay =
userCanSeeHistory && Number.isInteger(requested) && requested > 0
? { lookbackMs: Math.min(requested, 30_000) }
: false;
createCredentialProvider() in @useceleris/server does not send the suggestion to the signer automatically. Your claims callback reads request.replayLookbackMs and signs the replay claim. See Authentication for the endpoint.
5. Handle replayed messages
A replayed message looks like a live message. It has the same message ID and the same timestamp. The replay of a segment usually arrives before its new live messages. Do not depend on that order. Use your own versions or identifiers to order important data.
A message can arrive more than once. The SDK remembers 1,024 recent message IDs for each channel, for all its segments together. It drops a repeated ID before your listeners get it. That memory continues through automatic reconnects. An explicit connect() clears it.
- Use message IDs that are unique in the whole channel. The SDK drops a message with a repeated ID, also when it is in a different segment.
- A duplicate can still arrive after its ID leaves the memory of 1,024 IDs. Keep important actions idempotent in your application.
allowEcho: falsefilters your own messages only on the connection that published them. After a reconnect, the replay can include your own earlier messages.
A connection that falls more than 100 messages behind in a segment is closed by the server. The SDK then reconnects, and the new join replays the kept messages.
6. Plan for gaps
Replay can leave a gap when:
- The outage is longer than 2 minutes, or more than 100 messages arrived during it.
- The reconnect credentials have no
replayclaim, or a window that is too short. - History became unavailable after a service event.
After each automatic reconnect, the recovery event reports possibleGaps and possibleDuplicates. Both are always true. When you get the event, reload the important state from your application's API. Then continue with the live messages. See Reconnection and Recovery.
Next: Read Permissions.