Skip to content

Instantly share code, notes, and snippets.

@konstantin24121
Last active February 17, 2024 10:58
Show Gist options
  • Star 12 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save konstantin24121/49da5d8023532d66cc4db1136435a885 to your computer and use it in GitHub Desktop.
Save konstantin24121/49da5d8023532d66cc4db1136435a885 to your computer and use it in GitHub Desktop.
Telegram Bot 6.0 Validating data received via the Web App node implementation
const TELEGRAM_BOT_TOKEN = '110201543:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsaw'; // https://core.telegram.org/bots#creating-a-new-bot
export const verifyTelegramWebAppData = async (telegramInitData: string): boolean => {
// The data is a query string, which is composed of a series of field-value pairs.
const encoded = decodeURIComponent(telegramInitData);
// HMAC-SHA-256 signature of the bot's token with the constant string WebAppData used as a key.
const secret = crypto
.createHmac('sha256', 'WebAppData')
.update(TELEGRAM_BOT_TOKEN);
// Data-check-string is a chain of all received fields'.
const arr = encoded.split('&');
const hashIndex = arr.findIndex(str => str.startsWith('hash='));
const hash = arr.splice(hashIndex)[0].split('=')[1];
// sorted alphabetically
arr.sort((a, b) => a.localeCompare(b));
// in the format key=<value> with a line feed character ('\n', 0x0A) used as separator
// e.g., 'auth_date=<auth_date>\nquery_id=<query_id>\nuser=<user>
const dataCheckString = arr.join('\n');
// The hexadecimal representation of the HMAC-SHA-256 signature of the data-check-string with the secret key
const _hash = crypto
.createHmac('sha256', secret.digest())
.update(dataCheckString)
.digest('hex');
// if hash are equal the data may be used on your server.
// Complex data types are represented as JSON-serialized objects.
return _hash === hash;
};
@konstantin24121
Copy link
Author

Here a Node implementation for Telegram Bot 6.0 Web App init data validation https://core.telegram.org/bots/webapps#validating-data-received-via-the-web-app

@TopYar
Copy link

TopYar commented May 1, 2022

Example with crypto-js:

const CryptoJS = require("crypto-js")

export const verifyTelegramWebAppData = async (telegramInitData: string): Promise<boolean> => {
  const initData = new URLSearchParams(telegramInitData);
  const hash = initData.get("hash");
  let dataToCheck: string[] = [];
  
  initData.sort();
  initData.forEach((val, key) => key !== "hash" && dataToCheck.push(`${key}=${val}`));
  
  const secret = CryptoJS.HmacSHA256(TELEGRAM_BOT_TOKEN, "WebAppData");
  const _hash = CryptoJS.HmacSHA256(dataToCheck.join("\n"), secret).toString(CryptoJS.enc.Hex);
  
  return _hash === hash;
}

@konstantin24121
Copy link
Author

@TopYar great!

@Fuchsoria
Copy link

I added here package with golang implementation for verifying method https://github.com/Fuchsoria/telegram-webapps, feel free to test or use

@polRk
Copy link

polRk commented May 19, 2022

@Fuchsoria thank you so much!

@gornostay25
Copy link

Thank you!

@welljs
Copy link

welljs commented Feb 21, 2023

Example suggested by @TopYar wrapped in middleware for Nestjs/Fastify with nodejs native crypto

declarations.d.ts

declare module 'http' {
  interface IncomingHttpHeaders {
    'tg-init-data'?: string
  }
}

check-init-data.middleware

import {Injectable, Logger, NestMiddleware, UnauthorizedException} from '@nestjs/common';
import {FastifyRequest, FastifyReply} from 'fastify';
import * as crypto from 'crypto';

const {
  env: {
    TELEGRAM_BOT_TOKEN
  }
} = process;

@Injectable()
export class CheckInitDataMiddleware implements NestMiddleware {
  private readonly logger: Logger = new Logger(CheckInitDataMiddleware.name);

  use(req: FastifyRequest['raw'], res: FastifyReply['raw'], next: () => void) {
    this.logger.log(`Checking webapp init data`);

    const {headers: {['tg-init-data']: tgInitData}} = req;
    const initData = new URLSearchParams(tgInitData);
    const hashFromClient = initData.get('hash');
    const dataToCheck: string[] = [];

    initData.sort();
    initData.forEach((v, k) => k !== 'hash' && dataToCheck.push(`${k}=${v}`));

    const secret = crypto
      .createHmac('sha256', 'WebAppData')
      .update(TELEGRAM_BOT_TOKEN);

    const signature = crypto
      .createHmac('sha256', secret.digest())
      .update(dataToCheck.join('\n'));

    const referenceHash = signature.digest('hex');

    if (hashFromClient === referenceHash) {
      return next();
    }
    throw new UnauthorizedException('Invalid init data');
  }
}

@0snilcy
Copy link

0snilcy commented Mar 10, 2023

I love you! 💘

@raskyer
Copy link

raskyer commented Apr 10, 2023

Note that it seems to doesn't work when the user in telegram has a special character in its firstname or lastname.
Try it on your own :

  • In telegram add '&' in your firstname or lastname
  • try to connect on your webapp (with this auth mechanism)

You'll see that the hash generated by this method is different from the one generated by telegram

@motsgar
Copy link

motsgar commented May 21, 2023

Fixed the problem with '&' not working correctly:

const verifyInitData = (telegramInitData: string): boolean => {
    const urlParams = new URLSearchParams(telegramInitData);

    const hash = urlParams.get('hash');
    urlParams.delete('hash');
    urlParams.sort();

    let dataCheckString = '';
    for (const [key, value] of urlParams.entries()) {
        dataCheckString += `${key}=${value}\n`;
    }
    dataCheckString = dataCheckString.slice(0, -1);

    const secret = crypto.createHmac('sha256', 'WebAppData').update(process.env.API_TOKEN ?? '');
    const calculatedHash = crypto.createHmac('sha256', secret.digest()).update(dataCheckString).digest('hex');

    return calculatedHash === hash;
}

@leaftail1880
Copy link

@motsgar Thanks! <3

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment