---
title: Request Flow
description: How Telegram updates are processed through the Alita Robot pipeline.
---

This document details how Telegram updates flow through Alita Robot, from reception to response.

## Update Processing Pipeline

```
+-------------+     +----------------+     +------------+     +----------+
|  Telegram   | --> |  HTTP/Polling  | --> | Dispatcher | --> | Handlers |
|   Bot API   |     |    Receiver    |     |  (Router)  |     | (Modules)|
+-------------+     +----------------+     +------------+     +----------+
                                                |                   |
                                                |                   v
                                                |            +------+------+
                                                |            | Permission  |
                                                |            |   Checks    |
                                                |            +------+------+
                                                |                   |
                                                |                   v
                                                |            +------+------+
                                                |            |  Database   |
                                                |            |  / Cache    |
                                                |            +------+------+
                                                |                   |
                                                v                   v
                                          +----------+       +----------+
                                          |  Error   |       | Response |
                                          | Handler  |       |  to User |
                                          +----------+       +----------+
```

## Initialization Sequence

When the bot starts (`main.go`), it performs these steps in order:

1. **Health Check Mode**

    Optional

    If the `--health` flag is passed, the process performs an HTTP GET to `/health` and exits with the status code. This is used by Docker health checks.

    ```go
     if len(os.Args) > 1 && (os.Args[1] == "--health" || os.Args[1] == "-health") {
        // HTTP GET to /health, exit with status code
        os.Exit(0)
    }
    ```

2. **Version Check**

    Optional

    If the `--version`, `-version`, or `-v` flag is passed, the process prints the bot version and exits immediately without initializing any services.

    ```go
     if len(os.Args) > 1 && (os.Args[1] == "--version" || os.Args[1] == "-version" || os.Args[1] == "-v") {
        fmt.Println(config.AppConfig.BotVersion)
        os.Exit(0)
    }
    ```

3. **Panic Recovery Setup**

    Safety

    A top-level deferred recovery ensures the process logs the panic and exits cleanly rather than crashing silently.

    ```go
    defer func() {
        if r := recover(); r != nil {
            log.Errorf("[Main] Panic recovered: %v", r)
            os.Exit(1)
        }
    }()
    ```

4. **Cache Initialization**

    Infrastructure

    The Redis cache is initialized with connection retry logic (exponential backoff). Startup fails if Redis is unavailable.

    ```go
    if err := cache.InitCache(); err != nil {
        log.Fatalf("Failed to initialize cache: %v", err)
    }
    ```

5. **Locale Manager Initialization**

    i18n

    The singleton `LocaleManager` loads embedded YAML translation files for all supported languages.

    ```go
    localeManager := i18n.GetManager()
    localeManager.Initialize(&Locales, "locales", i18n.DefaultManagerConfig())
    ```

6. **OpenTelemetry Tracing Initialization**

    Observability

    Sets up distributed tracing with OTLP or console exporters based on environment configuration (`OTEL_EXPORTER_OTLP_ENDPOINT`, `OTEL_SERVICE_NAME`, `OTEL_TRACES_SAMPLE_RATE`).

    ```go
    tracing.InitTracing()
    ```

7. **HTTP Transport Configuration**

    Networking

    Connection pooling is configured for outbound HTTP requests to the Telegram API.

    ```go
    httpTransport := &http.Transport{
        MaxIdleConns:        config.AppConfig.HTTPMaxIdleConns,
        MaxIdleConnsPerHost: config.AppConfig.HTTPMaxIdleConnsPerHost,
        IdleConnTimeout:     120 * time.Second,
        ForceAttemptHTTP2:   true,
    }
    ```

8. **Bot Client Creation**

    Core

    The gotgbot client is initialized with the configured HTTP transport, a
    30-second timeout, and the optional custom Bot API URL.

    ```go
    b, err := gotgbot.NewBot(config.AppConfig.BotToken, &gotgbot.BotOpts{
        BotClient: &gotgbot.BaseBotClient{
            Client: http.Client{Transport: transport, Timeout: 30 * time.Second},
            DefaultRequestOpts: &gotgbot.RequestOpts{
                APIURL: resolveBotAPIURL(config.AppConfig.ApiServer),
            },
        },
    })
    ```

9. **Initial Checks**

    Validation

    Ensures the bot user exists in the database before modules load, providing
    the foreign-key anchor used by downstream records. Configuration has already
    been loaded and validated by the config package.

    ```go
    if err := alita.InitialChecks(b); err != nil {
        log.Fatalf("Initial checks failed: %v", err)
    }
    ```

10. **Dispatcher Creation**

    Core

    The dispatcher routes updates to handlers with bounded concurrency. It uses a `TracingProcessor` wrapper for trace context propagation in polling mode.

    ```go
    dispatcher := ext.NewDispatcher(&ext.DispatcherOpts{
        Error:       errorHandler,
        MaxRoutines: config.AppConfig.DispatcherMaxRoutines,
        Processor:   tracing.TracingProcessor{},
    })
    ```

11. **Monitoring Systems**

    Observability

    Monitoring systems are started based on configuration.

    ```go
    if config.AppConfig.EnableBackgroundStats {
        statsCollector = monitoring.NewBackgroundStatsCollector()
    }
    if config.AppConfig.EnablePerformanceMonitoring {
        autoRemediation = monitoring.NewAutoRemediationManager(statsCollector)
    }
    activityMonitor = monitoring.NewActivityMonitor() // Always starts
    ```

12. **HTTP Server & Mode Selection**

    Core

    The unified HTTP server registers health, metrics, and optionally pprof endpoints. Then either webhook or polling mode is activated.

    ```go
    httpServer := httpserver.New(config.AppConfig.HTTPPort, appStartTime)
    httpServer.RegisterHealth()
    httpServer.RegisterMetrics()
    httpServer.RegisterDBMetrics()
    if config.AppConfig.EnablePPROF {
        httpServer.RegisterPPROF()
    }

    if config.AppConfig.UseWebhooks {
        httpServer.RegisterWebhook(b, dispatcher, secret, domain)
    } else {
        updater.StartPolling(b, pollingOpts)
    }
    ```

## Module Loading Order

After dispatcher creation and before Telegram updates are accepted, `postInit`
loads modules and starts the persisted captcha lifecycle:

```go
func LoadModules(dispatcher *ext.Dispatcher) {
    // Reset the help registry (under its lock)
    modules.ResetHelpRegistry()

    // Load help LAST (deferred) to collect all commands
    defer modules.LoadHelp(dispatcher)

    // Loads all registered modules in priority order
    modules.LoadAllModules(dispatcher)
}

alita.LoadModules(dispatcher)
if err := modules.StartCaptchaLifecycle(bot); err != nil {
    log.Fatalf("[Captcha] Failed to start lifecycle: %v", err)
}
```

Captcha startup recovery releases incomplete attempts, finalizes expired
attempts, and reschedules still-valid attempts. It then starts the periodic
attempt-cleanup and scheduled-unmute workers. A recovery failure aborts startup
instead of accepting updates with an unknown captcha state.
Challenge photo/text delivery failures use the same rollback: release the attempt
and restore permissions, without retrying the send or generating a replacement.
Only successful cleanup marks that challenge skipped, logs one warning on the
update context, and lets the join continue to welcome. Cleanup failures propagate
to the dispatcher and retain the durable scheduled-unmute retry.
Captcha setting writes also ensure their foreign-key chat row with the Telegram
title supplied by commands, settings callbacks, or panel access, enriching
previously empty chat names without a second chat write.
Synthetic member IDs never trigger captcha membership lookups; known missing
members skip the join challenge, and missing/left/banned members receive no
timeout moderation or fallback unmute.

Registered modules, in priority order: BotUpdates, AISpam, SpamDB, Languages, Admin, Approvals, Pins, LogChannels, Misc, Bans, Mutes, Purges, Users, Reports, Dev, Locks, Filters, Antiflood, Notes, Connections, Disabling, Rules, Warns, Greetings, Captcha, AntiRaid, Federations, Blacklists, Reactions, Formatting, Backup. Help loads last.

## Handler Registration Pattern

Each module registers commands through the command pipeline (`helpers.WrapCommand`) and other handlers through gotgbot's handler system. Abridged from `alita/modules/bans.go`:

```go
func LoadBans(dispatcher *ext.Dispatcher) {
    // Register module in help system (takes the registry lock)
    SetModuleEnabled(bansModule.moduleName, true)
    initBanDescs() // fills each descriptor's RequiredChecks

    // Command handlers: WrapCommand runs the descriptor's checks first
    helpers.WrapCommand(dispatcher, banDesc, pipelineHandler(bansModule.ban))
    helpers.WrapCommand(dispatcher, kickDesc, pipelineHandler(bansModule.kick))
    helpers.WrapCommand(dispatcher, unbanDesc, pipelineHandler(bansModule.unban))

    // Callback query handlers
    dispatcher.AddHandler(handlers.NewCallback(
        callbackquery.Prefix("restrict"),
        bansModule.restrictButtonHandler,
    ))
}

func init() {
    RegisterLegacyModule("Bans", 70, LoadBans)
    initBanDescs()
    // Anonymous admins bypass WrapCommand; this re-runs the same checks after they prove admin rights
    RegisterAnonymousAdminHandler("ban", anonPipelineHandler(banDesc, bansModule.ban))
}
```

### Handler Types

| Type | Registration | Trigger |
|------|--------------|---------|
| Command (pipeline) | `helpers.WrapCommand(dispatcher, desc, handler)` | `/cmd` messages, after the descriptor's `RequiredChecks` |
| Command (legacy) | `handlers.NewCommand("cmd", fn)` | `/cmd` messages |
| Callback | `handlers.NewCallback(filter, fn)` | Button presses |
| Message | `handlers.NewMessage(filter, fn)` | Text messages |
| ChatMember | `handlers.NewChatMember(filter, fn)` | Other members' status changes |
| MyChatMember | `handlers.NewMyChatMember(filter, fn)` | The bot's own member status changes |

### Handler Groups

Handlers can be assigned to groups for priority control:

```go
// Negative group = higher priority (runs first)
dispatcher.AddHandlerToGroup(handler, -10)

// Group 0 = default
dispatcher.AddHandler(handler)  // Same as group 0

// Positive group = lower priority
dispatcher.AddHandlerToGroup(handler, 10)
```

:::tip[Handler group conventions]
- **Negative groups**: Early interception — captcha (-10), fed-ban (-6), antiraid (-5), spamdb (-4), admin-cache refresh (-2), bot-join and user logging (-1)
- **Group 0**: Standard command handlers (default)
- **Group 1**: connected-chat guard (ends processing after a PM command swaps to the connected group)
- **Positive groups (3-11)**: Message watchers — aispam (3), antiflood (4), locks (5-6), blacklists (7), reports and reactions (8), filters (9), pins (10), log-channel /setlog capture (11)

For the complete precedence table with propagation behavior, see [Handler Group Precedence](/architecture/handler-groups).
:::

## Permission Check Flow

Most admin commands follow this permission checking pattern:

```go
func (m moduleStruct) ban(b *gotgbot.Bot, ctx *ext.Context) error {
    chat := ctx.EffectiveChat
    user := ctx.EffectiveSender.User
    msg := ctx.EffectiveMessage

    // 1. Require group chat (not private)
    if !chat_status.RequireGroup(b, ctx, nil) {
        return ext.EndGroups
    }

    // 2. Require user to be admin
    if !chat_status.RequireUserAdmin(b, ctx, nil, user.Id) {
        return ext.EndGroups
    }

    // 3. Require bot to be admin
    if !chat_status.RequireBotAdmin(b, ctx, nil) {
        return ext.EndGroups
    }

    // 4. Check specific permission (restrict members)
    if !chat_status.CanUserRestrict(b, ctx, nil, user.Id) {
        return ext.EndGroups
    }

    // 5. Check bot has same permission
    if !chat_status.CanBotRestrict(b, ctx, nil) {
        return ext.EndGroups
    }

    // Proceed with ban logic...
}
```

### Permission Functions Reference

| Function | Purpose | When to Use |
|----------|---------|-------------|
| `RequireGroup` | Ensures chat is group/supergroup | Group-only commands |
| `RequirePrivate` | Ensures chat is private | PM-only commands |
| `RequireUserAdmin` | User must be admin | Admin commands |
| `RequireBotAdmin` | Bot must be admin | Commands needing bot admin |
| `RequireUserOwner` | User must be creator | Owner-only commands |
| `CanUserRestrict` | User can ban/mute | Ban/mute commands |
| `CanBotRestrict` | Bot can ban/mute | Ban/mute commands |
| `CanUserDelete` | User can delete messages | Purge commands |
| `CanBotDelete` | Bot can delete messages | Purge commands |
| `CanUserPin` | User can pin messages | Pin commands |
| `CanBotPin` | Bot can pin messages | Pin commands |
| `CanUserPromote` | User can promote/demote | Admin management |
| `CanBotPromote` | Bot can promote/demote | Admin management |
| `IsUserAdmin` | Check if user is admin | Conditional logic |
| `IsUserInChat` | Check if user is member | User validation |
| `IsUserBanProtected` | Check if user is protected | Before ban/kick |

## Response Patterns

### Handler Return Values

```go
// Stop processing, no more handlers run
return ext.EndGroups

// Continue to subsequent handler groups
return ext.ContinueGroups

// Error propagates to dispatcher error handler
return err
```

### Response Actions

```go
// Reply to the triggering message
msg.Reply(b, "Response text", &gotgbot.SendMessageOpts{
    ParseMode: formatting.HTML,
})

// Send new message to chat
b.SendMessage(chat.Id, "Message text", nil)

// Edit existing message
msg.EditText(b, &gotgbot.EditMessageTextOpts{Text: "New text"})

// Delete message
msg.Delete(b, nil)

// Answer callback query
query.Answer(b, &gotgbot.AnswerCallbackQueryOpts{
    Text: "Notification text",
})
```

## Async Processing

:::tip[When to use async]
Only use async processing for non-critical background tasks that do not require user confirmation. Operations that need success feedback must be synchronous.
:::

Non-critical operations can be processed asynchronously:

```go
// Fire and forget (with panic recovery)
go func() {
    defer func() {
        if r := recover(); r != nil {
            log.Error("Panic in async operation")
        }
    }()

    // Async work here
}()

// With timeout protection
go func() {
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()

    select {
    case <-time.After(2 * time.Second):
        // Do delayed work
    case <-ctx.Done():
        log.Warn("Operation timed out")
    }
}()
```

## Error Handling in Request Flow

### Dispatcher Error Handler

```go
Error: func(b *gotgbot.Bot, ctx *ext.Context, err error) ext.DispatcherAction {
    // 1. Recover from panics
    defer error_handling.RecoverFromPanic("DispatcherErrorHandler", "Main")

    // 2. Extract context for logging
    logFields := log.Fields{
        "update_id":  ctx.UpdateId,
        "error_type": fmt.Sprintf("%T", err),
    }

    // 3. Check for expected/suppressible errors
    if helpers.IsExpectedTelegramError(err) {
        log.WithFields(logFields).Warn("Expected Telegram API error")
        return ext.DispatcherActionNoop
    }

    // 4. Log the error
    log.WithFields(logFields).Error("Handler error")

    // 5. Continue processing other updates
    return ext.DispatcherActionNoop
}
```

### Common Error Patterns

```go
// Return to the dispatcher, which classifies and logs the error once.
// Do not also log it in the command or its deep-link helpers.
if err != nil {
    return err
}

// Log but continue (non-fatal)
if err != nil {
    log.Warn("Non-fatal error:", err)
}

// Silent failure for expected cases
_, _ = msg.Delete(b, nil)  // Ignore delete errors
```

:::tip[Expected Telegram errors]
The `helpers.IsExpectedTelegramError()` function filters common non-actionable errors like "bot is not an admin", "chat was deleted", and "message to delete not found". These are logged at Warn level instead of Error to reduce noise.
:::

The Bot API transport counts unexpected HTTP failures by method and status.
`getChatMember` responses identifying a missing user/member are negative
membership lookups, not operational errors; permission and chat-access errors
still count. Failed responses on traced calls carry a bounded
`telegram.error.reason` category, never the raw description or request text.
The bounded `alita_telegram_api_error_reasons_total{method,code,reason}` counter
also counts unexpected HTTP errors on requests without a trace context.

Membership probes reject chat IDs and Telegram's synthetic sender accounts
before contacting the API; the existing anonymous-admin proof rules still apply.
After a valid anonymous-admin confirmation, permissions use the confirming
admin for that chat only. The original message sender remains anonymous so
warning records do not disclose the confirming admin.
Shared admin-cache loads retain the originating trace but use independent
timeouts, so one canceled waiter cannot cancel other callers' lookup.

The production BotClient enables `AllowSendingWithoutReply` for ordinary
same-chat text replies, including commands with nil or custom reply options.
Deleting the triggering message therefore does not discard the response.
Cross-chat and ephemeral replies retain Telegram's stricter reply semantics.

## Next Steps

- [Module Pattern](/architecture/module-pattern) - Creating new feature modules
- [Caching](/architecture/caching) - Redis cache integration
- [Project Structure](/architecture/project-structure) - File organization
