Skip to content

Instantly share code, notes, and snippets.

@konstantin24121
Last active March 28, 2025 17:36
Show Gist options
  • 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;
};
@corck
Copy link

corck commented Mar 7, 2025

A Elixir example based on @TheBlackHacker python example.

defmodule TelegramValidator do
  @telegram_bot_token "BOT_TOKEN"

  @doc """
  Verifies the authenticity of Telegram Web App data.

  Takes a URL-encoded query string containing Telegram initialization data
  and verifies its authenticity using HMAC-SHA256.

  Returns a boolean indicating whether the data is valid.
  """
  @spec verify_telegram_web_app_data(String.t()) :: boolean()
  def verify_telegram_web_app_data(telegram_init_data) do
    # Parse the query string
    init_data = URI.decode_query(telegram_init_data)

    # Get hash_value from the query string
    hash_value = Map.get(init_data, "hash")

    # Sort key-value pairs alphabetically, excluding the hash
    data_to_check =
      init_data
      |> Map.drop(["hash"])
      |> Enum.sort()
      |> Enum.map(fn {key, value} -> "#{key}=#{value}" end)
      |> Enum.join("\n")

    # HMAC Calculation
    secret =
      :crypto.mac(:hmac, :sha256, "WebAppData", @telegram_bot_token)

    calculated_hash =
      :crypto.mac(:hmac, :sha256, secret, data_to_check)
      |> Base.encode16(case: :lower)

    calculated_hash == hash_value
  end
end

# Test with the same data as in the Python version
test_data = "initdata"
IO.puts("Validation result: #{TelegramValidator.verify_telegram_web_app_data(test_data)}")

I released a tiny hex package for validation: https://hexdocs.pm/telegram_miniapp_validation/readme.html

For anyone implementing a new version, what I realized after several attempts that sorting the nested user fields is a problem, specifically if you parse it into an internal map / object and then create a String again.

@Fuchsoria
Copy link

https://github.com/Fuchsoria/telegram-webapps zero dependency golang package is now updated to actual state

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