@telegram-apps/init-data-node
The package provides utilities to work with the initialization data of Telegram Mini Apps on the server side. To learn more about the initialization data and its usage, please refer to the documentation.
Installation
pnpm i @telegram-apps/init-data-node
npm i @telegram-apps/init-data-node
yarn add @telegram-apps/init-data-node
Parsing
To parse a value as init data, use the parse
method.
The method accepts init data presented as a string
or URLSearchParams
.
import { parse } from '@telegram-apps/init-data-node';
try {
const initData = parse('...');
// {
// canSendAfter: 10000,
// chat: {
// id: 1,
// type: 'group',
// username: 'my-chat',
// title: 'chat-title',
// photoUrl: 'chat-photo',
// },
// chatInstance: '888',
// chatType: 'sender',
// queryId: 'QUERY',
// receiver: {
// addedToAttachmentMenu: false,
// allowsWriteToPm: true,
// firstName: 'receiver-first-name',
// id: 991,
// isBot: false,
// isPremium: true,
// languageCode: 'ru',
// lastName: 'receiver-last-name',
// photoUrl: 'receiver-photo',
// username: 'receiver-username',
// },
// startParam: 'debug',
// user: {
// addedToAttachmentMenu: false,
// allowsWriteToPm: false,
// firstName: 'user-first-name',
// id: 222,
// isBot: true,
// isPremium: false,
// languageCode: 'en',
// lastName: 'user-last-name',
// photoUrl: 'user-photo',
// username: 'user-username',
// },
// }
} catch (e) {
console.error('Something is wrong', e);
}
Validating
validate
To validate the signature of the initialization data, the validate
function is used. It expects the initialization data to be passed in a raw format (search parameters) and throws an error in certain cases.
import { validate } from '@telegram-apps/init-data-node';
const secretToken = '5768337691:AAH5YkoiEuPk8-FZa32hStHTqXiLPtAEhx8';
const initData =
'query_id=AAHdF6IQAAAAAN0XohDhrOrc' +
'&user=%7B%22id%22%3A279058397%2C%22first_name%22%3A%22Vladislav%22%2C%22last_name%22%3A%22Kibenko%22%2C%22username%22%3A%22vdkfrost%22%2C%22language_code%22%3A%22ru%22%2C%22is_premium%22%3Atrue%7D' +
'&auth_date=1662771648' +
'&hash=c501b71e775f74ce10e377dea85a7ea24ecd640b223ea86dfe453e0eaed2e2b2';
validate(initData, secretToken);
Function will throw an error in one of these cases:
ERR_AUTH_DATE_INVALID
:auth_date
is empty or not foundERR_HASH_INVALID
:hash
is empty or not foundERR_SIGN_INVALID
: signature is invalidERR_EXPIRED
: init data expired
Here is the code you could use to check the error type:
import { validate, isErrorOfType } from '@telegram-apps/init-data-node';
try {
validate('init-data', 'token');
} catch (e) {
if (isErrorOfType(e, 'ERR_SIGN_INVALID')) {
console.log('Sign is invalid');
}
}
By default, the function checks the expiration of the initialization data. The default expiration duration is set to 1 day (86,400 seconds). It is recommended to always check the expiration of the initialization data, as it could be stolen but still remain valid. To disable this feature, pass { expiresIn: 0 }
as the third argument.
validate3rd
The validate3rd
function is used to check if the passed init data was signed by Telegram. As well as the validate
function, this one accepts the init data in the same format.
As the second argument, it accepts the Telegram Bot identifier that was used to sign this init data.
The third argument is an object with the following properties:
expiresIn
is responsible for init data expiration validationtest: boolean
: should be equaltrue
if the init data was received in the test Telegram environment
Here is the usage example:
const initData =
'user=%7B%22id%22%3A279058397%2C%22first_name%22%3A%22Vladislav%20%2B%20-%20%3F%20%5C%2F%22%2C%22last_name%22%3A%22Kibenko%22%2C%22username%22%3A%22vdkfrost%22%2C%22language_code%22%3A%22ru%22%2C%22is_premium%22%3Atrue%2C%22allows_write_to_pm%22%3Atrue%2C%22photo_url%22%3A%22https%3A%5C%2F%5C%2Ft.me%5C%2Fi%5C%2Fuserpic%5C%2F320%5C%2F4FPEE4tmP3ATHa57u6MqTDih13LTOiMoKoLDRG4PnSA.svg%22%7D' +
'&chat_instance=8134722200314281151' +
'&chat_type=private' +
'&auth_date=1733584787' +
'&signature=zL-ucjNyREiHDE8aihFwpfR9aggP2xiAo3NSpfe-p7IbCisNlDKlo7Kb6G4D0Ao2mBrSgEk4maLSdv6MLIlADQ' +
'&hash=2174df5b000556d044f3f020384e879c8efcab55ddea2ced4eb752e93e7080d6';
const botId = 7342037359;
await validate3rd(initData, botId);
Function will throw an error in one of these cases:
ERR_AUTH_DATE_INVALID
:auth_date
is empty or not foundERR_SIGNATURE_MISSING
:signature
is empty or not foundERR_SIGN_INVALID
: signature is invalidERR_EXPIRED
: init data expired
WARNING
This function uses Web Crypto API instead of Node.js modules.
isValid
Alternatively, to check the init data validity, a developer could use the isValid
function. It doesn't throw an error, but returns a boolean value indicating the init data validity.
import { isValid } from '@telegram-apps/init-data-node';
if (isValid('... init data', 'my-bot-token')) {
console.log('Init data is fine');
}
isValid3rd
Does the same as the isValid
function, but checks if the init data was signed by Telegram:
import { isValid3rd } from '@telegram-apps/init-data-node';
const botId = 7342037359;
if (await isValid3rd('... init data', botId)) {
console.log('Init data is fine');
}
WARNING
This function uses Web Crypto API instead of Node.js modules.
Signing
There could be some cases when a developer needs to create their own init data. For instance, Telegram does not send this data automatically if you are using something like KeyboardButton
or InlineKeyboardButton
. Telegram cannot do this because it doesn't know which Telegram Bot token should be used.
To implement such a process, it is required to use the sign
method. Here is the complete example:
import { sign } from '@telegram-apps/init-data-node';
sign(
{
canSendAfter: 10000,
chat: {
id: 1,
type: 'group',
username: 'my-chat',
title: 'chat-title',
photoUrl: 'chat-photo',
},
chatInstance: '888',
chatType: 'sender',
queryId: 'QUERY',
receiver: {
addedToAttachmentMenu: false,
allowsWriteToPm: true,
firstName: 'receiver-first-name',
id: 991,
isBot: false,
isPremium: true,
languageCode: 'ru',
lastName: 'receiver-last-name',
photoUrl: 'receiver-photo',
username: 'receiver-username',
},
startParam: 'debug',
user: {
addedToAttachmentMenu: false,
allowsWriteToPm: false,
firstName: 'user-first-name',
id: 222,
isBot: true,
isPremium: false,
languageCode: 'en',
lastName: 'user-last-name',
photoUrl: 'user-photo',
username: 'user-username',
},
},
'5768337691:AAH5YkoiEuPk8-FZa32hStHTqXiLPtAEhx8',
new Date(1000),
);
'auth_date=1' +
'&can_send_after=10000' +
'&chat=%7B%22id%22%3A1%2C%22type%22%3A%22group%22%2C%22title%22%3A%22chat-title%22%2C%22photo_url%22%3A%22group%22%2C%22username%22%3A%22my-chat%22%7D' +
'&chat_instance=888' +
'&chat_type=sender' +
'&query_id=QUERY' +
'&receiver=%7B%22added_to_attachment_menu%22%3Afalse%2C%22allows_write_to_pm%22%3Atrue%2C%22first_name%22%3A%22receiver-first-name%22%2C%22id%22%3A991%2C%22is_bot%22%3Afalse%2C%22is_premium%22%3Atrue%2C%22language_code%22%3A%22ru%22%2C%22last_name%22%3A%22receiver-last-name%22%2C%22photo_url%22%3A%22receiver-photo%22%2C%22username%22%3A%22receiver-username%22%7D' +
'&start_param=debug' +
'&user=%7B%22added_to_attachment_menu%22%3Afalse%2C%22allows_write_to_pm%22%3Afalse%2C%22first_name%22%3A%22user-first-name%22%2C%22id%22%3A222%2C%22is_bot%22%3Atrue%2C%22is_premium%22%3Afalse%2C%22language_code%22%3A%22en%22%2C%22last_name%22%3A%22user-last-name%22%2C%22photo_url%22%3A%22user-photo%22%2C%22username%22%3A%22user-username%22%7D' +
'&hash=47cfa22e72b887cba90c9cb833c5ea0f599975b6ce7193741844b5c4a4228b40'
This function accepts three arguments:
- Data to sign: It represents a parsed init data object excluding the
authDate
andhash
properties. - Bot token: This token is received from @BotFather.
- Signing date: This value will be used as the value of the
authDate
property.
As a result, the function returns signed init data.
Web Crypto API
If this package is used in an environment other than Node.js, a developer can use the web
subdirectory, which exports the same methods as described above but returns promises.
import {
validate,
sign,
signData,
isValid,
} from '@telegram-apps/init-data-node/web';
await validate(...);
await sign(...);
await signData(...);
await isValid(...);
Passing Hashed Token
All package methods allow developers to use a hashed token instead of a raw token.
By "hashed token," we mean a token hashed using the HMAC-SHA-256 algorithm with a key derived from WebAppData
, as specified in the validation section of the documentation.
Here are some examples:
import { validate, sign } from '@telegram-apps/init-data-node';
const secretTokenHashed = 'a5c609aa52f63cb5e6d8ceb6e4138726ea82bbc36bb786d64482d445ea38ee5f';
const initData =
'query_id=AAHdF6IQAAAAAN0XohDhrOrc' +
'&user=%7B%22id%22%3A279058397%2C%22first_name%22%3A%22Vladislav%22%2C%22last_name%22%3A%22Kibenko%22%2C%22username%22%3A%22vdkfrost%22%2C%22language_code%22%3A%22ru%22%2C%22is_premium%22%3Atrue%7D' +
'&auth_date=1662771648' +
'&hash=c501b71e775f74ce10e377dea85a7ea24ecd640b223ea86dfe453e0eaed2e2b2';
// Validating.
validate(initData, secretTokenHashed, { tokenHashed: true });
// Signing.
sign(
{
canSendAfter: 10000,
chat: {
id: 1,
type: 'group',
username: 'my-chat',
title: 'chat-title',
photoUrl: 'chat-photo',
},
chatInstance: '888',
chatType: 'sender',
queryId: 'QUERY',
receiver: {
addedToAttachmentMenu: false,
allowsWriteToPm: true,
firstName: 'receiver-first-name',
id: 991,
isBot: false,
isPremium: true,
languageCode: 'ru',
lastName: 'receiver-last-name',
photoUrl: 'receiver-photo',
username: 'receiver-username',
},
startParam: 'debug',
user: {
addedToAttachmentMenu: false,
allowsWriteToPm: false,
firstName: 'user-first-name',
id: 222,
isBot: true,
isPremium: false,
languageCode: 'en',
lastName: 'user-last-name',
photoUrl: 'user-photo',
username: 'user-username',
},
},
secretTokenHashed,
new Date(1000),
{ tokenHashed: true }
);
You can use this approach to reduce the number of instances where you directly pass a raw token.