Skip to main content

Middleware

Middleware wraps every RPC the client makes, letting you retry, throttle, log or trace requests without touching call sites. You install middleware via Options.Middlewares, and they apply in order.

type Middleware interface {
Handle(next tg.Invoker) InvokeFunc
}

A middleware receives the next invoker and returns a function that calls it — the classic onion pattern. The most useful implementations live in gotd/contrib.

FLOOD_WAIT handling

Telegram rate-limits with FLOOD_WAIT errors that say "retry after N seconds". The floodwait middleware catches these and retries automatically so your code never sees them.

Waiter is the scheduler-based implementation for long-running clients. It both acts as a middleware and must wrap your run loop so it can schedule retries:

import "github.com/gotd/contrib/middleware/floodwait"

waiter := floodwait.NewWaiter().
WithCallback(func(ctx context.Context, wait floodwait.FloodWait) {
log.Printf("FLOOD_WAIT, retrying in %s", wait.Duration)
})

client := telegram.NewClient(appID, appHash, telegram.Options{
Middlewares: []telegram.Middleware{waiter},
})

// Run the client *inside* the waiter so retries can be scheduled.
return waiter.Run(ctx, func(ctx context.Context) error {
return client.Run(ctx, func(ctx context.Context) error {
// ... your work ...
return nil
})
})

For one-shot scripts, floodwait.NewSimpleWaiter() is a simpler timer-based variant that needs no Run wrapper. Both support WithMaxRetries and WithMaxWait.

Rate limiting

To stay under the limits in the first place, add the ratelimit middleware, which paces outgoing requests with a token bucket (golang.org/x/time/rate):

import (
"github.com/gotd/contrib/middleware/ratelimit"
"golang.org/x/time/rate"
)

telegram.Options{
Middlewares: []telegram.Middleware{
waiter, // retry on FLOOD_WAIT
ratelimit.New(rate.Every(time.Millisecond*100), 5), // ~10 req/s, burst 5
},
}

Order matters: putting the waiter first means it wraps (and retries through) the rate limiter. This pairing — proactive rate limiting plus reactive flood-wait retries — is the recommended setup for userbots, and is exactly what the userbot example uses.

Writing your own

A middleware is just a function. The pretty-print example logs every request and response with timing:

func logging() telegram.MiddlewareFunc {
return func(next tg.Invoker) telegram.InvokeFunc {
return func(ctx context.Context, input bin.Encoder, output bin.Decoder) error {
start := time.Now()
err := next.Invoke(ctx, input, output)
log.Printf("%T in %s (err=%v)", input, time.Since(start), err)
return err
}
}
}

See Debugging and tracing for more on inspecting traffic.