# How Callback Queries Are Processed in the Zakirullin Files Bot: A Complete Guide

> Learn how Zakirullin Files bot processes callback queries. Discover the centralized pipeline that detects commands, routes to handlers, and answers queries via the Telegram Bot API.

- Repository: [Artem Zakirullin/files.md](https://github.com/zakirullin/files.md)
- Tags: how-to-guide
- Published: 2026-05-21

---

**The Zakirullin Files Bot processes Telegram callback queries through a centralized pipeline in [`server/bot.go`](https://github.com/zakirullin/files.md/blob/main/server/bot.go) that detects the callback, extracts JSON-encoded commands, routes to specific handlers, and finally answers the query via the Telegram Bot API.**

The Zakirullin Files Bot handles interactive button presses in Telegram by treating callback queries as standard update events. When a user taps an inline keyboard button, the bot receives a callback query that must be parsed, routed to business logic, and acknowledged to Telegram's API. Understanding this flow requires examining the handler architecture in [`server/bot.go`](https://github.com/zakirullin/files.md/blob/main/server/bot.go) and the API wrapper in [`server/pkg/tg/tg.go`](https://github.com/zakirullin/files.md/blob/main/server/pkg/tg/tg.go).

## The Callback Query Processing Pipeline

The bot implements a seven-step pipeline that transforms an incoming Telegram update into executed business logic and user feedback.

### Entry Point and Detection

All updates—whether messages, inline queries, or callbacks—enter through the `(*Bot).Reply` method in [`server/bot.go`](https://github.com/zakirullin/files.md/blob/main/server/bot.go). The code checks for the presence of a callback identifier using `u.CallbackQueryID()`, which returns the query ID and a boolean flag indicating whether the update is a callback query.

```go
// server/bot.go (lines 84-86)
if callbackQueryID, ok := u.CallbackQueryID(); ok {
    // This is a callback query
}

```

### Command Extraction and Routing

Once detected, the bot extracts the command embedded in the callback data. Because Telegram imposes a strict 64-byte limit on `callback_data`, the bot uses compact command strings defined as constants like `CmdComplete` (`"c"`) and `CmdShare` (`"share"`). The `extractCmd` function unmarshals the JSON payload into a `tg.Cmd` struct, then looks up the command name in the `handlers()` map to retrieve the appropriate function.

```go
// server/bot.go (lines 66-68, 76-84)
cmd, err := b.extractCmd(u)
if err != nil {
    return err
}

handler, ok := b.handlers()[cmd.Name]
if !ok {
    return fmt.Errorf("unknown command: %s", cmd.Name)
}
err = handler(cmd.Params)

```

### Handler Execution

The mapped handler receives the command parameters and executes the business logic—for example, marking a file as complete via `CmdComplete`. This decouples the Telegram transport layer from domain operations, allowing handlers to focus purely on data manipulation without managing API communication.

### Answering the Callback

After handler execution, `Bot.Reply` retrieves the original `callbackQueryID` and invokes `b.tg.AnswerCallbackQuery(callbackQueryID, text)`. This sends a `tgbotapi.CallbackConfig` to Telegram's API, displaying a toast notification to the user. An empty string suppresses the notification while still satisfying API requirements.

```go
// server/bot.go (lines 87-95)
if callbackQueryID, ok := u.CallbackQueryID(); ok {
    err = handler(cmd.Params)
    if err != nil {
        return err
    }
    // Answer with empty string for silent acknowledgment
    _ = b.tg.AnswerCallbackQuery(callbackQueryID, "")
}

```

The low-level implementation resides in [`server/pkg/tg/tg.go`](https://github.com/zakirullin/files.md/blob/main/server/pkg/tg/tg.go), where `AnswerCallbackQuery` constructs the actual API request:

```go
// server/pkg/tg/tg.go (lines 70-76)
func (tg *TG) AnswerCallbackQuery(queryID, text string) error {
    _, err := tg.api.Send(tgbotapi.CallbackConfig{
        CallbackQueryID: queryID,
        Text:            text,
    })
    return err
}

```

## Creating Callback Buttons with 64-Byte Constraints

Inline keyboards are constructed using helpers from the `tg` package. Developers create buttons by encoding concise commands to stay within Telegram's payload limit. The `tg.NewCmd` function serializes the command and parameters into a JSON string that fits within the 64-byte restriction.

```go
import "github.com/zakirullin/files.md/server/pkg/tg"

func makeCompleteBtn(hash string) tg.Btn {
    // CmdComplete is the short identifier "c"
    cmd := tg.NewCmd(tg.CmdComplete, []string{hash})
    // Button displays with checkmark emoji and text
    return tg.NewBtn("✅ Complete", cmd)
}

```

Source: [`server/bot.go`](https://github.com/zakirullin/files.md/blob/main/server/bot.go) (lines 80-84)

When the user presses this button, Telegram sends a callback query containing the JSON representation of the `tg.Cmd` in the `CallbackData` field. The bot's `extractCmd` routine unmarshals this payload and routes execution to the appropriate handler.

## Key Implementation Files

| File | Role |
|------|------|
| [`server/bot.go`](https://github.com/zakirullin/files.md/blob/main/server/bot.go) | Central router that detects callbacks via `CallbackQueryID()`, extracts commands with `extractCmd()`, and orchestrates handler execution. |
| [`server/pkg/tg/tg.go`](https://github.com/zakirullin/files.md/blob/main/server/pkg/tg/tg.go) | Low-level Telegram API wrapper implementing `AnswerCallbackQuery()` to send `CallbackConfig` structs and complete the protocol handshake. |
| [`server/pkg/tg/cmd.go`](https://github.com/zakirullin/files.md/blob/main/server/pkg/tg/cmd.go) | Defines the `Cmd` struct and short string constants (e.g., `"c"`, `"share"`) used within the 64-byte callback data limit. |
| [`server/pkg/tg/keyboard.go`](https://github.com/zakirullin/files.md/blob/main/server/pkg/tg/keyboard.go) | Provides `NewBtn()` and keyboard builders that serialize commands into JSON for the `CallbackData` field. |

## Summary

- **Unified entry point**: All updates route through `(*Bot).Reply` in [`server/bot.go`](https://github.com/zakirullin/files.md/blob/main/server/bot.go), which detects callbacks via `u.CallbackQueryID()`.
- **Compact encoding**: Commands use single-character constants to respect the 64-byte `callback_data` limit imposed by Telegram.
- **Handler mapping**: The `handlers()` map routes parsed `tg.Cmd` structs to business logic functions like `CmdComplete`, keeping transport and domain logic separate.
- **Mandatory acknowledgment**: Every callback must be answered via `AnswerCallbackQuery()` in [`server/pkg/tg/tg.go`](https://github.com/zakirullin/files.md/blob/main/server/pkg/tg/tg.go) to prevent infinite loading states on buttons.

## Frequently Asked Questions

### Why does the Zakirullin Files Bot use single-character command codes?

Telegram restricts `callback_data` to 64 bytes. To maximize space for parameters while ensuring unique command identification, the bot defines short constants like `"c"` for complete and `"share"` for sharing, leaving more room for JSON-encoded arguments in the payload.

### What happens if a callback query is not answered?

Telegram displays a persistent loading clock icon on the button and may retry the request multiple times. The bot's `Bot.Reply` method ensures every callback receives an answer via `AnswerCallbackQuery()`, even if the response text is an empty string to suppress notifications.

### How does the bot differentiate between regular messages and callback queries?

The `Update` interface in [`server/bot.go`](https://github.com/zakirullin/files.md/blob/main/server/bot.go) provides `CallbackQueryID()`, which returns a non-empty string and `true` only for callback queries. This boolean check allows the same `Reply` method to handle both message updates and button presses transparently without separate routing logic.

### Where is the callback handler mapped to its execution function?

The `handlers()` method in [`server/bot.go`](https://github.com/zakirullin/files.md/blob/main/server/bot.go) returns a map that associates command names (e.g., `CmdComplete`) with handler functions. After `extractCmd` parses the callback data, the bot retrieves the corresponding function from this map and invokes it with `handler(cmd.Params)`.