PC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minuteiTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more
Telegram delivers each update to your bot as an HTTPS POST request and treats any response outside the 2xx range as a failed delivery to be retried. A secure PHP endpoint therefore runs in a fixed order: it checks the X-Telegram-Bot-Api-Secret-Token header before reading the body, decodes and validates the JSON, records the update_id under a unique constraint in the same transaction as the business changes, and returns a 2xx status only after that work is committed. The code below follows that sequence with plain PHP 8.0 or later, PDO, and no framework.
The Telegram Bot API reference, in its revision for Bot API 10.3 dated 24 August 2026, describes the push model in one sentence:
“Whenever there is an update for the bot, we will send an HTTPS POST request to the specified URL, containing a JSON-serialized Update.” (Telegram, Bot API documentation,
setWebhookdescription)What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
What you need before you start
- PHP 8.0 or later with the PDO extension, the PDO driver for your database, and the curl extension for the one-time registration script.
- A public HTTPS hostname with a certificate Telegram accepts. Telegram’s Bots FAQ states that redirects are not supported, so the URL you register must answer directly without a 301 or 302.
- A supported port. Telegram’s documented webhook ports are 443, 80, 88, and 8443. The webhook guide covers the TLS and public reachability requirements.
- A bot token issued by @BotFather, held in an environment variable or a protected secrets store, never in committed source.
- A transactional database. InnoDB, PostgreSQL, and SQLite all support the transaction and unique-constraint behaviour used here. MyISAM tables do not support transactions and will silently defeat the design.
Webhooks and polling are alternatives. Polling uses getUpdates, and Telegram does not allow it while a webhook is set. To return to polling later, remove the webhook first with deleteWebhook.
#1 Best Overall
Step 1: Generate a secret and register the webhook
Generate a high-entropy secret_token
The setWebhook method accepts a secret_token of 1 to 256 characters, limited to letters, digits, underscores, and hyphens. A 64-character hexadecimal string meets those rules and carries 256 bits of randomness:
php -r 'echo bin2hex(random_bytes(32)), PHP_EOL;'
Store the output as TELEGRAM_WEBHOOK_SECRET in your server environment. The same value must be available to the endpoint in Step 3.
Register the URL with setWebhook
Run this once from your deployment process, not from a publicly reachable page:
<?php
declare(strict_types=1);
$token = getenv('TELEGRAM_BOT_TOKEN') ?: '';
$secret = getenv('TELEGRAM_WEBHOOK_SECRET') ?: '';
$url = 'https://bot.example.com/telegram/webhook.php';
if ($token === '' || $secret === '') {
fwrite(STDERR, "Missing TELEGRAM_BOT_TOKEN or TELEGRAM_WEBHOOK_SECRETn");
exit(1);
}
$ch = curl_init("https://api.telegram.org/bot{$token}/setWebhook");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 15,
CURLOPT_POSTFIELDS => [
'url' => $url,
'secret_token' => $secret,
'max_connections' => 20,
'allowed_updates' => json_encode(['message', 'callback_query']),
],
]);
$response = curl_exec($ch);
if ($response === false) {
fwrite(STDERR, curl_error($ch) . "n");
exit(1);
}
echo $response, "n";
The values for max_connections and allowed_updates are examples. Set max_connections to what your server can handle concurrently, and list only the update types your bot uses.
Expected result: a JSON object with "ok":true and a description. If the response is "ok":false, read the description field first; it usually names the invalid URL, port, or token format.
Rank #2
Step 2: Confirm the registration with getWebhookInfo
A successful setWebhook call only confirms that Telegram stored the configuration. It does not prove that delivery works. Check the live state:
curl -s "https://api.telegram.org/bot$TELEGRAM_BOT_TOKEN/getWebhookInfo"
Look at these fields in the response, as described in the Bot API reference:
Free tools Windows power users keep installed
One-click scans. No signup required.
urlshould match the URL you registered exactly.pending_update_countshould stay near zero under normal traffic. A rising count means Telegram cannot deliver updates.last_error_dateandlast_error_messagereport the most recent delivery failure.- The synchronization error fields report problems Telegram encountered when applying the configuration.
Step 3: Authenticate the request before doing anything else
Telegram sends the secret in the X-Telegram-Bot-Api-Secret-Token header. In PHP, the server exposes it as $_SERVER['HTTP_X_TELEGRAM_BOT_API_SECRET_TOKEN'] under the usual CGI mapping: hyphens become underscores and the name is prefixed with HTTP_ and uppercased. Confirm this on your own stack, because some proxy and server configurations drop or rewrite headers with unusual names. If getallheaders() is available in your SAPI, you can read the header from it instead, but the $_SERVER key is the more portable check.
<?php
declare(strict_types=1);
$expectedSecret = getenv('TELEGRAM_WEBHOOK_SECRET') ?: '';
$providedSecret = $_SERVER['HTTP_X_TELEGRAM_BOT_API_SECRET_TOKEN'] ?? '';
if ($expectedSecret === '' || $providedSecret === '' || !hash_equals($expectedSecret, $providedSecret)) {
http_response_code(403);
exit;
}
Three details matter here:
hash_equals()compares strings in time that does not depend on where they first differ. The PHP manual entry for hash_equals requires the known string first and the user-supplied string second, which is the order used above.- The empty-string checks are deliberate. A missing environment variable must not turn into a valid match against an empty header.
- Reject unauthenticated requests before reading the body. An unauthenticated caller should not be able to trigger JSON parsing or database work.
Telegram’s FAQ suggests a secret path as a way to hide the endpoint. Treat a path as defence in depth only. The header check is the actual authentication, and the header is the current mechanism in the Bot API.
Step 4: Read and validate the JSON body
Read the raw body from php://input, not from $_POST, which is empty for JSON payloads. Telegram’s Hello Bot sample uses the same raw-body pattern, but it is written to show API basics and is not a production handler.
$raw = file_get_contents('php://input');
if ($raw === false || $raw === '') {
http_response_code(400);
exit;
}
if (strlen($raw) > 1048576) {
http_response_code(413);
exit;
}
try {
$update = json_decode($raw, true, 512, JSON_THROW_ON_ERROR);
} catch (JsonException) {
http_response_code(400);
exit;
}
if (!is_array($update) || !isset($update['update_id']) || !is_int($update['update_id'])) {
http_response_code(400);
exit;
}
// Application-level check: when a message is present, require an integer chat id.
$message = $update['message'] ?? null;
if ($message !== null && (!is_array($message) || !is_int($message['chat']['id'] ?? null))) {
http_response_code(400);
exit;
}
The 1 MiB limit is a local choice to bound memory use, not a Telegram requirement. The PHP JSON functions with JSON_THROW_ON_ERROR let you handle malformed input in one catch block rather than checking json_last_error() after each call.
Do not rely on filter_input() for validation. Its default filter is FILTER_UNSAFE_RAW, which performs no filtering, as the filter_input manual page documents. Validate types explicitly, as above, and apply your own range and format checks to any field you store.
Step 5: Make each update idempotent
Telegram retries after any non-2xx response, and the Bot API does not promise exactly-once delivery. Your handler must therefore produce the same result whether it sees an update once or several times. The method below does that with a database unique constraint on update_id, which is more reliable than a “check, then insert” sequence because two concurrent deliveries can both pass the check.
Create the dedupe table
This is MySQL syntax. PostgreSQL and SQLite use the same primary key with their own type names.
CREATE TABLE telegram_updates (
update_id BIGINT NOT NULL PRIMARY KEY,
received_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB;
If one database serves several bots, make the key (bot_id, update_id). Telegram numbers updates per bot, so the same number can appear for two different bots.
Rank #4
Insert the marker and the business changes in one transaction
function processUpdate(PDO $pdo, array $update): int
{
$pdo->beginTransaction();
try {
$insert = $pdo->prepare('INSERT INTO telegram_updates (update_id) VALUES (:id)');
$insert->execute([':id' => $update['update_id']]);
} catch (PDOException $e) {
if ($pdo->inTransaction()) {
$pdo->rollBack();
}
// SQLSTATE class 23 is an integrity-constraint violation, meaning this update was already recorded.
return str_starts_with((string) $e->getCode(), '23') ? 200 : 500;
}
try {
applyBusinessChanges($pdo, $update); // your application logic, on the same connection
$pdo->commit();
return 200;
} catch (Throwable $e) {
if ($pdo->inTransaction()) {
$pdo->rollBack();
}
error_log('Update ' . $update['update_id'] . ' failed: ' . $e->getMessage());
return 500;
}
}
$pdo = new PDO($dsn, $dbUser, $dbPass, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_EMULATE_PREPARES => false,
]);
http_response_code(processUpdate($pdo, $update));
The PDO transactions documentation explains that transaction support depends on the driver and the database engine. Confirm both with your own test before relying on rollback.
Why the marker goes first
Placing the insert first means only one delivery can own the update. If a second delivery of the same update arrives while the first is still running, the database makes it wait on the unique key. Once the first transaction commits, the second insert fails with an integrity error and returns 200 without repeating any business effect. If the first transaction rolls back, the marker disappears with it, and the second delivery processes the update normally. This behaviour holds on engines that lock the conflicting key, including InnoDB and PostgreSQL.
Catch the integrity error only around the insert. If you wrap the whole block, a foreign-key or check-constraint failure inside applyBusinessChanges() could be mistaken for a duplicate and acknowledged, which silently drops the update.
Side effects that the database cannot roll back
A rollback undoes only what the database did. If your handler also calls the Bot API, for example to send a reply with sendMessage, that message is already gone when a later step fails. Telegram then redelivers the update, and the reply is sent again. Two patterns avoid this:
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Scan for outdated or missing drivers - takes under a minute3Repair Windows errors before they cause bigger problems- Send after commit. Simple, but a crash between the commit and the send loses the reply.
- Outbox table. Write the outgoing message into a table in the same transaction as the marker, then send it from a worker that marks rows as sent. This gives at-least-once sending, and the worker needs its own duplicate check if a duplicate send is unacceptable.
Choose the response for each outcome
The status code is the only feedback Telegram receives, so it should match what actually happened:
| Situation | HTTP status | What Telegram does | Why |
|---|---|---|---|
| Secret header missing or mismatched | 403 | Treats the delivery as failed and retries | Nothing was processed, and the request should not reach the database. |
| Body larger than the local limit | 413 | Treats the delivery as failed and retries | Protects memory. Real Telegram updates are far smaller than the limit used here. |
Invalid JSON or missing update_id from an authenticated request |
400 | Treats the delivery as failed and retries | A retry will fail the same way. Log the size and a hash of the body so the cause can be traced without storing the payload. |
| New update, committed | 200 | Records success; no retry | The update is durably accepted. |
Duplicate update_id |
200 | Records success; no retry | The earlier delivery already handled it. |
| Business step failed and was rolled back | 500 | Treats the delivery as failed and retries | Safe to retry because the marker was rolled back too. |
A 400 response for malformed JSON is a judgement call. It keeps the failure visible in Telegram’s error fields, but it also means Telegram will keep retrying a payload that cannot succeed. If that noise becomes a problem, log the failure and return 200 instead, accepting that the payload is then discarded.
Synchronous processing or a durable queue
The processing function above runs inside the web request. That is the simplest design, and it is appropriate when handlers are short. If a handler calls slow external services, a queue can keep response times short and move retries out of Telegram’s hands.
| Aspect | Synchronous, inside the request | Durable enqueue, then a worker |
|---|---|---|
| Response latency | Grows with the work done in the handler | Short: one insert, then return 200 |
| Failure and retry | A 500 makes Telegram redeliver, and the handler runs again | The worker retries; Telegram sees only that the update was accepted |
| Transaction boundary | Marker and business changes commit together | Queue row and marker commit together; business changes commit in the worker |
| Operational needs | Only the web PHP process | A worker process or scheduled job, plus monitoring of queue depth |
| Best fit | Short handlers and simple hosting | Slow external calls, long-running work, or bursts of updates |
In the queued design, the worker needs the same idempotency rules as the endpoint, because it can also fail after doing part of its work.
Recommended Free Tools
Common mistakes to avoid
- Checking for an existing update with a SELECT and then inserting. Two deliveries can both pass the check. Use the unique constraint.
- Returning 200 before the marker and business changes are committed. A crash then loses the update, and Telegram will not retry because it saw success.
- Treating every database error as a duplicate. Only the integrity-class error from the marker insert means “already seen”. Other failures must return 500.
- Using an in-memory array or cache as the only dedupe store. It disappears on restart and is not shared across PHP-FPM workers or servers.
- Logging the secret or the bot token. Log the update ID and the error message, not the headers or the full payload.
- Using the Hello Bot sample as a complete handler. It demonstrates the API, not authentication, validation, idempotency, or failure handling.
- Assuming that a successful
setWebhookmeans delivery works. CheckgetWebhookInfoand send a test message through the bot.
Troubleshooting with getWebhookInfo
pending_update_countkeeps rising. Telegram is not receiving 2xx responses. Readlast_error_message, then check your web server error log and the status codes your endpoint returns.- The error message points to a connection or certificate problem. Confirm the port is one of the documented ports, the certificate chain is complete, and the URL responds without a redirect.
- Genuine updates return 403. Either the header name does not map to
HTTP_X_TELEGRAM_BOT_API_SECRET_TOKENon your stack, or the environment secret differs from the one passed tosetWebhook. After rotating the secret, register the webhook again with the new value. - Business effects happen twice. Check that the unique constraint exists, that the table uses a transactional engine, and that
applyBusinessChanges()uses the same PDO connection as the marker insert. - Polling calls fail. The webhook is still set. Call
deleteWebhookbefore usinggetUpdates.
Keep getWebhookInfo output out of public pages and shared logs, since it reveals the configured URL and recent error text.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

