A store manager who refreshes the orders page every few minutes, or a contact form whose email lands in spam, are two classic cases. The simpler way is for the site itself to send a message to Telegram. With a bot and a few lines of PHP this is doable. The key is to store the bot token properly and make sure a problem with Telegram never causes a problem with an order.
This article is part of the complete Telegram guide. Before you start, you need to have created a bot and have its token; for that step see creating a Telegram bot.
A simple architecture#
The flow works like this:
- An event happens on the site (a new order or a form submission).
- The site's code sends an HTTPS request to the Telegram API.
- The bot posts the message in the admins' group or a private channel.
You need three things: the bot token, the chat ID (chat_id) and outbound access from your server to api.telegram.org. Some services restrict access by country, and some hosts limit outbound connections; if messages don't arrive, first ask your hosting provider about outbound connectivity.
For the destination, create a private admin group, add the bot and send a message. Find the ID with getUpdates. For a channel, the bot must be an admin.
Rule one: keep the token in configuration#
The token must not be in files reachable from the web or in a public database. In WordPress, the best place is the wp-config.php file:
// wp-config.php (above the line "That's all, stop editing!")
define( 'DI9_TG_TOKEN', 'your-bot-token-here' );
define( 'DI9_TG_CHAT_ID', '-1001234567890' );If your host allows it, put the values in environment variables and read them with getenv(). Do not write the token inside a plugin file that you commit to a code repository.
Sending messages safely from WordPress#
Put this code in a small plugin (not in the theme's functions.php, because it disappears when you change themes). The base function sends an HTML message and logs errors without revealing the token:
<?php
/**
* Plugin Name: Site Telegram Notifications
*/
defined( 'ABSPATH' ) || exit;
function di9_tg_send( string $html ): bool {
if ( ! defined( 'DI9_TG_TOKEN' ) || ! defined( 'DI9_TG_CHAT_ID' ) ) {
return false;
}
$response = wp_remote_post(
'https://api.telegram.org/bot' . DI9_TG_TOKEN . '/sendMessage',
array(
'timeout' => 5,
'body' => array(
'chat_id' => DI9_TG_CHAT_ID,
'text' => $html,
'parse_mode' => 'HTML',
'disable_web_page_preview' => true,
),
)
);
if ( is_wp_error( $response ) ) {
error_log( 'Telegram notify failed: ' . $response->get_error_code() );
return false;
}
if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
error_log( 'Telegram notify HTTP ' . wp_remote_retrieve_response_code( $response ) );
return false;
}
return true;
}
function di9_tg_esc( string $text ): string {
return htmlspecialchars( $text, ENT_NOQUOTES, 'UTF-8' );
}A few points in this code matter:
- The
timeoutis short so a slow Telegram does not hold up the page. - The log contains only the error code, not the full URL. The request URL includes the token and must not go into logs.
htmlspecialcharsmakes dynamic text safe for Telegram's HTML mode. Without it, a customer name containing<would break the message.
WooCommerce new order alert#
If you run WooCommerce, hook into the checkout order-processed action:
add_action( 'woocommerce_checkout_order_processed', 'di9_tg_new_order', 10, 1 );
function di9_tg_new_order( int $order_id ): void {
$order = wc_get_order( $order_id );
if ( ! $order ) {
return;
}
$lines = array();
$lines[] = '<b>New order #' . di9_tg_esc( (string) $order->get_order_number() ) . '</b>';
$lines[] = 'Customer: ' . di9_tg_esc( $order->get_formatted_billing_full_name() );
$lines[] = 'Total: ' . di9_tg_esc( wp_strip_all_tags( $order->get_formatted_order_total() ) );
$lines[] = 'Status: ' . di9_tg_esc( wc_get_order_status_name( $order->get_status() ) );
di9_tg_send( implode( "\n", $lines ) );
}Send only the information the manager needs. Don't push phone numbers, addresses and payment details to Telegram for no reason; the group may have many members and messages stay on third-party servers. For setting up the store itself, see setting up a store with WooCommerce.
A simple example for a contact form#
If you have a custom form, send the message after validation and a nonce check:
add_action( 'admin_post_nopriv_di9_contact', 'di9_handle_contact' );
add_action( 'admin_post_di9_contact', 'di9_handle_contact' );
function di9_handle_contact(): void {
if ( ! isset( $_POST['_wpnonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ), 'di9_contact' ) ) {
wp_die( 'Invalid request', '', array( 'response' => 403 ) );
}
$name = isset( $_POST['name'] ) ? sanitize_text_field( wp_unslash( $_POST['name'] ) ) : '';
$message = isset( $_POST['message'] ) ? sanitize_textarea_field( wp_unslash( $_POST['message'] ) ) : '';
if ( '' === $name || '' === $message ) {
wp_safe_redirect( wp_get_referer() ?: home_url( '/' ) );
exit;
}
di9_tg_send( '<b>New message from the contact form</b>' . "\n" . di9_tg_esc( $name ) . "\n" . di9_tg_esc( mb_substr( $message, 0, 500 ) ) );
wp_safe_redirect( add_query_arg( 'sent', '1', wp_get_referer() ?: home_url( '/' ) ) );
exit;
}The HTML form needs a hidden action field with the value di9_contact and wp_nonce_field( 'di9_contact' ), and must submit to admin-post.php. A public form is a spam target; in practice add anti-spam such as a CAPTCHA or a rate limiter, otherwise spam bots will flood your group.
Laravel#
In Laravel, put the token in .env and read it through a config file:
// config/services.php
'telegram' => [
'token' => env('TELEGRAM_BOT_TOKEN'),
'chat_id' => env('TELEGRAM_CHAT_ID'),
],use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
function notifyTelegram(string $html): void
{
$token = config('services.telegram.token');
$chat = config('services.telegram.chat_id');
if (! $token || ! $chat) {
return;
}
try {
$response = Http::timeout(5)->asForm()->post(
"https://api.telegram.org/bot{$token}/sendMessage",
[
'chat_id' => $chat,
'text' => $html,
'parse_mode' => 'HTML',
]
);
if ($response->failed()) {
Log::warning('Telegram notify failed', ['status' => $response->status()]);
}
} catch (\Throwable $e) {
Log::warning('Telegram notify exception', ['class' => $e::class]);
}
}For a high-traffic site, run this function inside a queued Job so the user's request does not wait for Telegram. Make dynamic text safe with e() or htmlspecialchars. Keep the .env file out of Git, and the public public folder must not expose it.
After setup#
- Test the site: place a test order and check that the message arrives.
- Keep Telegram's sending limits in mind and don't send in bulk.
- If the token leaks, revoke it in BotFather and put the new value in your configuration.
- For scheduled jobs such as a daily report, use cron on your host.
Testing and troubleshooting#
After installing the code, place a test order. If no message arrives, check these in order:
- Are the constants in
wp-config.phpdefined correctly? One wrong character is enough to break it. - Is the bot in the group, and an admin of the channel?
- Is the chat ID correct? Group IDs are usually negative.
- Did the PHP error log or the WordPress log record anything? Enabling
WP_DEBUG_LOGon a staging site helps. Don't leave it on longer than necessary on a live site. - Does the server have outbound access to
api.telegram.org? Some hosts restrict outbound connections.
For building a clean, safe plugin, building a WordPress plugin from scratch is a good foundation. For overall site security, also review the WordPress security checklist.
What not to send#
No sensitive data should be in the message: passwords, card details, verification codes, or anything that would cause serious harm if leaked. An alert should only say "something happened" and give the order number; the manager sees the details in the site's admin panel. If a message must contain customer information, keep the group private and limit its members to the people who need it.
Alternative: a ready-made plugin#
If you don't want to write code, there are ready-made plugins that send forms or orders to Telegram. Before installing, check a few things: is it still maintained? Where does it store the token? Does it send more information than needed to an outside service? A plugin that asks for several unnecessary permissions for a simple job, or has not been updated for a long time, is not a good choice. Remember that every extra plugin increases the attack surface; for a simple need, the few lines of code above are more under your control.
Summary#
- The site sends a message to the bot's API with one HTTPS request; you need the token, the chat ID and outbound access.
- Put the token in
wp-config.php, an environment variable or.env, not in a public file. - Escape dynamic text for Telegram's HTML mode.
- Use a short timeout and log errors without printing the token so orders are never disrupted.
- Send only the information that is needed, and protect public forms with a nonce and anti-spam.
- For high traffic, hand the sending off to a queue.