Skip to Content
ConceptsinitConnection fingerprints

initConnection fingerprints

The first request of every Telegram connection is invokeWithLayer(initConnection(...)). What a client puts there, together with the transport it speaks and the way it numbers its messages, is its fingerprint on the wire. opentele2 fills those values from an API profile, validates them, and checks a live session against them.

The parameters

initConnection fieldFrom the profileNotes
api_idapi_idmust be the official id of the lang_pack in strict mode
device_model, system_versionthe devicenever empty
app_versionthe app versionnever empty; its major must match the known version in strict mode
lang_codelang_codeat least two characters
system_lang_codesystem_lang_codeneeds a region (en-US) for Android and iOS
lang_packlang_packmust be an official pack
params—Telegram Desktop sends {"tz_offset": <seconds>}

The layer opentele2 advertises is 230, the schema of Telegram Desktop 7.3.1 (LAYER in all three packages).

Per client library. The opentele2 Telethon TelegramClient sends every field, lang_pack included (Telethon itself leaves it empty), and params with tz_offset for the tdesktop pack. initConnectionOptions gives mtcute the same fields and params. grammers 0.11 builds initConnection itself and always sends an empty lang_pack and no params, so connection_params can set every field but those two.

Validation

validate.py
from opentele2.fingerprint import validate_init_connection_params issues = validate_init_connection_params( 1, "Desktop", "Windows 11 x64", "7.3.1 x64", "en-US", "tdesktop", "en", strict=True ) print(issues) # ["api_id 1 does not match lang_pack 'tdesktop' (expected 2040)"]

The opentele2 TelegramClient validates its own parameters on construction with its fingerprint_config (default DEFAULT_CONFIG, warn mode). In warn mode a bad profile shows up as a warning. The client never raises for a fingerprint issue: in strict mode it catches the ValueError and only logs it at debug level. To stop before connecting, call validate_params in strict mode yourself:

from opentele2.fingerprint import FingerprintConfig, StrictMode strict = FingerprintConfig(strict_mode=StrictMode.STRICT) strict.validate_params( # raises ValueError on any issue api_id=1, device_model="Desktop", system_version="Windows 11 x64", app_version="7.3.1 x64", system_lang_code="en-US", lang_pack="tdesktop", lang_code="en", )

Every finding is an issue with a kind, a severity and a message worded the same in every package.

KindChecked
UnknownLangPackalways: lang_pack is not an official pack
ShortLangCodealways: lang_code has fewer than two characters
MissingRegionalways: an Android or iOS system_lang_code without a region
EmptyDeviceModel, EmptySystemVersion, EmptyAppVersionalways
ApiIdMismatchstrict: api_id is not the official id of the lang_pack
AppVersionMajorstrict: the app version major of android, ios, tdesktop and macos profiles
AndroidSdkRangestrict: the Android SDK level is outside the known range

The mode decides what happens with them:

ModeSeverityPython validate_paramsPython TelegramClient
offnotenever warns or raisesstays silent
warn (default)warningwarnings.warn with the messagewarns on construction
stricterrorraises ValueErrorcatches the error and logs it at debug level

tz_offset

Telegram Desktop sends the local UTC offset in seconds as tz_offset, wrapped by whole days into −12 h … +14 h and rounded to a multiple of 900 seconds. The core computes it from the offset you pass, so the result never depends on the machine running the code.

tz.py
from opentele2.fingerprint import tdesktop_tz_offset print(tdesktop_tz_offset()) # this machine's local offset, for example 10800.0

The opentele2 TelegramClient recomputes it on every connect(), so a daylight saving change between connections shows up as it does in Telegram Desktop.

msg_id offset

Official clients add a small random offset to their message ids. FingerprintConfig draws one in 0..=0xFFFF from the operating system’s random source (randomize_msg_id_offset, on by default); random.seed and other seeded generators do not reproduce it. is_valid_msg_id / isValidMsgId tells whether an id is plausible: positive, odd from the client and even from the server, with its upper 32 bits within 300 seconds of now.

PythonNodeRust
Draw an offsetgenerate_msg_id_offset()msgIdOffset()fingerprint::msg_id_offset_for_os()
Check an idis_valid_msg_id(msg_id, from_client=True)isValidMsgId(msgId, fromClient, nowUnix)fingerprint::is_valid_msg_id(msg_id, from_client, now_unix)

Layer

get_advertised_layer() (Python), LAYER (Node) and api::LAYER (Rust) are 230. In Python, get_recommended_layer() returns the layer of the installed Telethon and warns when it differs from 230 by more than 15 layers (LAYER_TOLERANCE); FingerprintConfig(layer_override=...) replaces the layer get_effective_layer() reports. A mismatched layer breaks deserialisation, so override it only with a library built for that layer.

Transport recommendation

Transport recommendation

Every official client speaks the obfuscated transport. TransportRecommendation.get_connection_class() returns Telethon's ConnectionTcpObfuscated, and the opentele2 TelegramClient uses it unless you pass connection=. TransportRecommendation.get_available_transports() lists the Telethon transports (full, abridged, intermediate, obfuscated) that import.

Consistency checks

Validation looks at the values you are about to send; the consistency checks ask the server how it sees a live session. They run seven requests and checks in a fixed order:

CheckPasses when
get_confighelp.getConfig succeeds
nearest_dchelp.getNearestDc succeeds
current_sessionthe server marks the current session official_app
layer_matchthe library’s layer is within 15 of the advertised 230
lang_packlangpack.getLanguages accepts the session’s lang_pack; an empty pack passes without a request
app_updatehelp.getAppUpdate succeeds
terms_of_servicehelp.getTermsOfServiceUpdate succeeds
checks.py
# client: a connected opentele2 TelegramClient, inside a coroutine report = await client.RunConsistencyChecks() # auto_warn=True warns when a check fails print(report.all_passed) print(report.summary) # one [OK] or [FAIL] line per check
Note:

current_session fails for any session whose api_id is not official: the server reports such sessions without official_app. A failing check is a signal to look at the profile, not an error of the library.

Last updated on