Hash Symbol Not Displaying in Telegram Bot Custom Keyboard

I’m having trouble with my Telegram bot custom keyboard. When I try to add a hash symbol (#) to my keyboard button text, it doesn’t show up properly. The button appears but the hash character is missing.

I’ve seen other bots that can display the hash symbol in their keyboard buttons without any issues, so I know it’s possible somehow.

What am I doing wrong? Is there a special way to encode the hash character for Telegram keyboards?

$buttonText = "#option 1"; // The hash symbol here

$keyboardLayout[] = [
    ["text" => $buttonText]
];

$customKeyboard = [
    "keyboard" => $keyboardLayout,
    "resize_keyboard" => true
];

$response = file_get_contents("https://api.telegram.org/bot" . BOT_TOKEN . "/sendMessage?chat_id=" 
    . $chatId . "&text=" . urlencode("Main Menu") . "&reply_markup=" . json_encode($customKeyboard));

try escaping the hash with a backslash like # or use html entities # instead. telegram api can be picky with special chars in json strings sometimes, had same issue with @ symbols before

I encountered a similar problem few months back. It stems from using urlencode() incorrectly, which encodes the hash symbol (#) as %23, leading to JSON parsing issues on Telegram’s end. A more effective solution is to use cURL for sending the request as a POST, incorporating the reply_markup in the body rather than in the URL. This approach resolved my hash symbol issue and is generally more reliable for handling special characters effectively.

The issue you’re experiencing is likely related to how the JSON is being constructed and sent. When you use json_encode() on the keyboard array and then append it directly to the URL string, certain characters can get mangled during the process. Instead of trying different escape methods, consider setting proper headers and sending the data as a structured POST request. I had this exact problem with hash symbols disappearing from my bot’s inline keyboards last year. The solution was to use proper content-type headers (application/json) and send the entire payload as JSON rather than mixing URL parameters with JSON strings. This ensures that special characters like # are preserved correctly throughout the transmission process.