Reliability

Error handling

Every RPC error from Telegram is a typed Go error you can switch on. Common ones have helpers; the rest you match by string. There is a database of 600+ codes with when-and-why explanations to look up anything unfamiliar.

ErrResponseCode

Errors from RPC calls arrive as *gogram.ErrResponseCode, a small struct with four fields:

  • Code — the HTTP-style status (400, 401, 403, 404, 420, 500, 303).
  • Message — the canonical error name (PEER_ID_INVALID, FLOOD_WAIT_X).
  • Description — the formatted human-readable message gogram knows about.
  • AdditionalInfo — the parsed parameter for codes ending in _X (e.g. the seconds for FLOOD_WAIT_X).
_, err := client.SendMessage("@nope", "hello")
if err != nil {
	log.Println(err)
	// [PEER_ID_INVALID] The peer is invalid (code 400)
}
_, err := client.SendMessage("@nope", "hello")
if err != nil {
	log.Println(err)
	// [PEER_ID_INVALID] The peer is invalid (code 400)
}

Matching on codes

import "errors"

_, err := client.SendMessage(peerID, "hello")
if err != nil {
	var rpc *gogram.ErrResponseCode
	if errors.As(err, &rpc) {
		switch rpc.Message {
		case "PEER_ID_INVALID":
			return handleStalePeer()
		case "USER_IS_BLOCKED":
			return nil // ignore
		case "CHAT_WRITE_FORBIDDEN":
			return promoteBot()
		}
	}
	return err
}
import "errors"

_, err := client.SendMessage(peerID, "hello")
if err != nil {
	var rpc *gogram.ErrResponseCode
	if errors.As(err, &rpc) {
		switch rpc.Message {
		case "PEER_ID_INVALID":
			return handleStalePeer()
		case "USER_IS_BLOCKED":
			return nil // ignore
		case "CHAT_WRITE_FORBIDDEN":
			return promoteBot()
		}
	}
	return err
}

Always match on the wire code, not on the human-readable text. Side-by-side:

Fragile
if strings.Contains(err.Error(), "peer is invalid") {
	// breaks the moment gogram tweaks the wording,
	// or Telegram localises the error text in a release
	return handleStalePeer()
}
Stable
var rpc *gogram.ErrResponseCode
if errors.As(err, &rpc) && rpc.Message == "PEER_ID_INVALID" {
	return handleStalePeer()
}

Message is the wire constant Telegram defines and is stable across releases. Description is what gogram formats for humans and may evolve.

Parameterized errors

Twenty-one error codes carry a parameter on the tail. gogram parses the parameter out and exposes it on AdditionalInfo:

EFLOOD_WAIT_XEFILE_MIGRATE_XESLOWMODE_WAIT_XEPHONE_MIGRATE_XESTORY_SEND_FLOOD_WEEKLY_X

var rpc *gogram.ErrResponseCode
if errors.As(err, &rpc) && strings.HasPrefix(rpc.Message, "FLOOD_WAIT") {
	seconds := rpc.AdditionalInfo.(int)
	time.Sleep(time.Duration(seconds) * time.Second)
	// retry
}
var rpc *gogram.ErrResponseCode
if errors.As(err, &rpc) && strings.HasPrefix(rpc.Message, "FLOOD_WAIT") {
	seconds := rpc.AdditionalInfo.(int)
	time.Sleep(time.Duration(seconds) * time.Second)
	// retry
}

Most migrations are handled for you automatically — you never see a FILE_MIGRATE escape into your code. Flood waits and slowmode waits do escape when the wait exceeds SleepThresholdMs; that is your cue to back off.

Centralised handlers

For project-wide retry policy, plug a single function into ClientConfig.ErrorHandler:

client, _ := telegram.NewClient(telegram.ClientConfig{
	// ...
	ErrorHandler: func(err error) bool {
		var rpc *gogram.ErrResponseCode
		if errors.As(err, &rpc) && rpc.Message == "PEER_ID_INVALID" {
			cache.InvalidatePeer()
			return true // retry the call once
		}
		return false
	},
})
client, _ := telegram.NewClient(telegram.ClientConfig{
	// ...
	ErrorHandler: func(err error) bool {
		var rpc *gogram.ErrResponseCode
		if errors.As(err, &rpc) && rpc.Message == "PEER_ID_INVALID" {
			cache.InvalidatePeer()
			return true // retry the call once
		}
		return false
	},
})

Return true to ask gogram to retry the call once, false to surface the error to the caller. The same shape exists for FloodHandler— it only sees flood waits.

Full error reference

Every code gogram knows about, with a hand-written when-and-why explanation, lives in the errors database. Search by code or by topic; each code has its own page with a Go snippet for handling it.