Skip to main content

Update recovery

The plain dispatcher delivers updates as they arrive, but Telegram's update stream is stateful: each update carries a sequence number, and a client that was briefly offline can miss updates or receive them out of order. Recovering those gaps means tracking state and calling updates.getDifference / updates.getChannelDifference at the right moments.

The updates package — the "gap engine" — does this for you.

Wiring it up

The manager sits between the client and your dispatcher. It needs to both receive updates (as the UpdateHandler) and observe RPC responses (as a middleware), so it is installed in two places:

import (
"github.com/gotd/td/telegram"
"github.com/gotd/td/telegram/updates"
updhook "github.com/gotd/td/telegram/updates/hook"
"github.com/gotd/td/tg"
)

d := tg.NewUpdateDispatcher()
gaps := updates.New(updates.Config{
Handler: d, // your dispatcher receives recovered, ordered updates
Logger: logzap.New(log.Named("gaps")),
})

client, err := telegram.ClientFromEnvironment(telegram.Options{
UpdateHandler: gaps,
Middlewares: []telegram.Middleware{
updhook.UpdateHook(gaps.Handle),
},
})

Running the engine

After authenticating, start the engine with your user ID. It blocks, keeping the client alive and processing updates until the context is cancelled:

return client.Run(ctx, func(ctx context.Context) error {
// ... authenticate ...
self, err := client.Self(ctx)
if err != nil {
return err
}
return gaps.Run(ctx, client.API(), self.ID, updates.AuthOptions{
IsBot: self.Bot,
OnStart: func(ctx context.Context) {
log.Info("recovery started")
},
})
})

Your dispatcher handlers (OnNewMessage, OnNewChannelMessage, …) are registered exactly as before — the difference is they now receive a gap-free, correctly ordered stream.

Persisting state

By default the engine keeps state in memory, so a restart re-syncs from scratch. For a long-running service, give it a Storage (and access-hash storage) so it resumes from where it left off. The gotd/contrib module provides bbolt-backed implementations:

gaps := updates.New(updates.Config{
Handler: d,
Storage: boltstor.NewStateStorage(boltdb),
})

The userbot example uses persistent state; the updates example is a minimal in-memory version.

Known limits

The engine relies on the server for getDifference correctness and cannot recover from ChannelDifferenceTooLong (it resyncs that channel instead). Stateless updates can't be ordered. These are inherent to the MTProto update model.