Parse, validate and sign Telegram Mini App init data.
Init data is the signed string a Mini App receives in
window.Telegram.WebApp.initData and sends to your backend. Validate it before
trusting anything inside it.
pip install init-data-pyThe core has no dependencies. Validating with Telegram's public key instead of your bot token needs one extra:
pip install "init-data-py[ed25519]"Requires Python 3.10 or newer.
The usual case, for a backend that owns the bot.
from init_data_py import validate_by_hash
init_data = validate_by_hash(query_string, bot_token)
print(init_data.user.id)validate_by_hash returns the parsed init data, or raises an error if it is
invalid.
Init data expires after one day by default. Pass expires_in to change it, or
0 to turn the check off. Turning it off lets an old query string be replayed
forever, so prefer a short lifetime.
from datetime import timedelta
validate_by_hash(query_string, bot_token, expires_in=3600)
validate_by_hash(query_string, bot_token, expires_in=timedelta(hours=1))
validate_by_hash(query_string, bot_token, expires_in=0) # no expiry checkIf you only want True or False, use is_valid_by_hash:
from init_data_py import is_valid_by_hash
if not is_valid_by_hash(query_string, bot_token):
raise PermissionErrorA third party that needs to trust init data for a bot it does not own can
verify Telegram's Ed25519 signature instead. This needs the ed25519 extra,
and only clients from Bot API 8.0 onwards send the signature field.
from init_data_py import validate_by_signature
init_data = validate_by_signature(query_string, bot_id)
init_data = validate_by_signature(query_string, bot_id, environment="test")is_valid_by_signature is the boolean form.
parse only reads the query string. It does not validate anything, so do not
trust the result until a validator has passed.
from init_data_py import parse
init_data = parse(query_string)
init_data.user # a User, or None
init_data.chat # a Chat, or None
init_data.issued_at # auth_date as a UTC datetime
init_data.extra # fields this version does not know aboutUseful for tests and fixtures. Real init data comes from Telegram.
from init_data_py import UnsignedInitData, User, sign
query_string = sign(
UnsignedInitData(user=User(id=5167898484, first_name="xin")),
bot_token,
)Everything raised by this library inherits from errors.InitDataError.
Everything caused by bad init data inherits from errors.InvalidInitDataError.
The ones you will see most:
ParseError: the query string is malformed or a required field is missingHashInvalidError: the hash does not matchSignatureInvalidError: the signature does not matchExpiredError: the init data is too old
from init_data_py import errors, validate_by_hash
try:
init_data = validate_by_hash(query_string, bot_token)
except errors.ExpiredError:
...
except errors.InvalidInitDataError:
...| 0.2.x | 1.0 |
|---|---|
InitData.parse(qs) |
parse(qs) |
InitData.from_query_string(qs) |
parse(qs) |
init_data.validate(token) |
validate_by_hash(qs, token) |
init_data.validate(token, raise_error=False) |
is_valid_by_hash(qs, token) |
init_data.validate(token, lifetime=3600) |
validate_by_hash(qs, token, expires_in=3600) |
InitData(user=user).sign(token) |
sign(UnsignedInitData(user=user), token) |
from init_data_py.types import User |
from init_data_py import User |
Other changes:
- Init data now expires after one day by default. Pass
expires_in=0to keep the old behaviour. - Fields the library does not recognise no longer cause an error.
- The old error names are kept as aliases, so existing
exceptclauses keep working.
This project uses uv.
uv sync --extra ed25519 # set up the environment
uv run python -m unittest discover --verbose # run the tests
uv run ruff check . # lint
uv run ruff format . # format
uv run mypy # type checkThis library is licensed under the MIT License.