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 forFLOOD_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:
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()
}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.
