October DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsPC HealthRecommendedCrashes, freezes, slowdowns? Check your PC nowSpot repairable issues before they interrupt work.Check PCOctober DealsAmazon USDeal season is back - check today's better picksAmazon US: current deals, useful picks and tech finds.See Picks×
Skip to content
World desk8 min

Sending Telegram Bot Messages with PHP cURL: Handling HTTP Status, JSON Errors, and Telegram’s ok Field

A successful curl_exec() does not mean Telegram accepted your message. Here is how to check cURL transport, the HTTP status, JSON decoding, and Telegram's ok field in PHP, and what to log without exposing the bot token.
Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

To send a Telegram bot message from PHP with cURL, POST to the sendMessage method, then judge the outcome in four separate checks: did the cURL transfer complete, what HTTP status came back, does the body decode as JSON, and does Telegram’s ok field report success. A response body is not proof that Telegram accepted the message. curl_exec() only tells you that the transfer finished, and a successful transfer can still carry an error from Telegram.

Request shape: the URL and the body formats

Telegram Bot API methods are called over HTTPS at https://api.telegram.org/bot<token>/METHOD_NAME, where <token> is your bot token and METHOD_NAME is the method, such as sendMessage. The API accepts GET and POST requests. Parameters can travel in the query string, as form-encoded fields, as a JSON body, or as multipart form data. Multipart is the format for file uploads; text-only calls such as sendMessage can use JSON.

As an Amazon Associate I earn from qualifying purchases.

  • Query string (GET): suits simple read-only calls with short parameter lists.
  • Form-encoded (POST): a conventional choice when you build the body from key-value pairs.
  • JSON body (POST with Content-Type: application/json): the format used in the example below. Nested values such as reply markup can be sent without manual encoding.
  • Multipart form data (POST): required when you upload a file, for example a photo or document.

The bot token sits inside the URL path, which has a direct consequence for logging. Anything that records the URL records the token. The logging section covers how to avoid that.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

A sendMessage call in PHP

The example below sends a text message as JSON and performs the checks in the order a failure would surface: transport first, then HTTP status, then JSON decoding, then Telegram’s own result.

<?php
declare(strict_types=1);

$url = 'https://api.telegram.org/bot' . $token . '/sendMessage';
$payload = json_encode([
    'chat_id' => $chatId,
    'text' => $text,
], JSON_THROW_ON_ERROR);

$ch = curl_init($url);
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => $payload,
    CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 20,
]);

$body = curl_exec($ch);
if ($body === false) {
    $errno = curl_errno($ch);
    $error = curl_error($ch);
    curl_close($ch);
    throw new RuntimeException("cURL transport failure ($errno): $error");
}

$httpStatus = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

try {
    $response = json_decode($body, true, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
    throw new RuntimeException("Telegram response was not valid JSON (HTTP $httpStatus)", 0, $e);
}

if (!is_array($response)) {
    throw new RuntimeException("Telegram response JSON was not an object (HTTP $httpStatus)");
}

if (($response['ok'] ?? false) !== true) {
    $code = $response['error_code'] ?? 'unknown';
    $description = $response['description'] ?? 'No description supplied';
    throw new RuntimeException("Telegram API error ($code): $description; HTTP $httpStatus");
}

$message = $response['result'];

This is a starting point, not a complete production client. It throws on every failure so that the caller decides what to do next. Retry logic, queueing, and user-facing messages belong in the layer that calls it.

Layer 1: the cURL transport result

The first question is whether the request completed at all. With CURLOPT_RETURNTRANSFER set to true, curl_exec() returns the response body as a string when the transfer succeeds, and returns false on a transport failure. Transport failures include DNS resolution errors, refused or dropped connections, TLS problems, and timeouts.

When the return value is false, read the failure with curl_errno() and curl_error() before calling curl_close(). The example does this. A timeout, for instance, appears here as a cURL error rather than as an HTTP status, so a timed-out request never reaches the status check.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

A non-false return value means only that bytes arrived. It says nothing about whether the status was 200, whether the body is JSON, or whether Telegram accepted the call.

Layer 2: the HTTP status code

Read the status separately with curl_getinfo($ch, CURLINFO_HTTP_CODE), and read it before closing the handle. The PHP manual for curl_exec states: “Note that response status codes which indicate errors (such as 404 Not found) are not regarded as failure. curl_getinfo() can be used to check for these.”

In practice, a 404 or 500 does not make curl_exec() return false. Your code has to decide what a non-2xx status means. The example records the status and passes it into the error message, but it does not branch on it. That is a deliberate gap: some applications will treat a non-2xx status as a hard failure, while others will rely on the JSON body for detail. Choose one policy and apply it consistently.

Keep the status even when the body is useful. The status is the only transport-level evidence of what the server said, and it is what makes an error message diagnosable later.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Layer 3: decoding the body safely

Do not assume the body is JSON. Use json_decode() with JSON_THROW_ON_ERROR, which makes PHP throw a JsonException instead of setting the global JSON error state that older code had to check with json_last_error(). Wrap the call in a try block, as the example does, so that the failure is handled in one place.

Malformed bodies happen in practice. An empty body, a plain-text or HTML page returned by a proxy or an intermediate server, or a truncated response will all fail to decode. Such a body can arrive with any status, including a 200, when something other than Telegram answered. Treat a decode failure as its own error category, and keep the HTTP status with it so you can tell a proxy error page from a Telegram error.

When you log a decode failure, keep a short excerpt of the body, for example the first few hundred bytes, so you can see what arrived. Bound the excerpt; do not log whole bodies by default.

$excerpt = substr($body, 0, 300);
error_log("Telegram non-JSON response, HTTP $httpStatus, excerpt: $excerpt");

The example also checks that the decoded value is an array. Valid JSON is not necessarily an object, and an unexpected scalar would otherwise cause confusing notices at the next step.

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.

Layer 4: Telegram’s ok field, result, and error details

Telegram’s Bot API reference states: “The response contains a JSON object, which always has a Boolean field ‘ok’ and may have an optional String field ‘description’ with a human-readable description of the result.” This is the authoritative check for whether the method succeeded. A 200 status with ok set to false is a failed call, and an HTTP error with a valid JSON body still carries Telegram’s explanation.

Field Present when What it means for your code
ok Always Boolean. true means the method succeeded; false means it failed. Compare with !== true, not a truthiness test on another field.
result ok is true The method’s output. For sendMessage, this is the sent message object. Check that the key exists before reading it.
description Optional; supplied on failures and may be present otherwise A human-readable explanation. Use it for logs and diagnostics, not as a fixed string to match on.
error_code Returned on failures An integer. Telegram’s reference says its contents are subject to change in the future, so do not build a permanent mapping from numbers to behavior.
parameters Optional An object that can help automate error handling. Inspect the keys your real responses contain rather than assuming a fixed set.

Because error_code is not a stable catalogue, this article does not provide a complete table of error codes or the correct response to each one. Log the code and the description together, and when you add a specific branch, base it on a response you have actually observed.

Note also that the example uses ?? 'unknown' and similar fallbacks. Those fallbacks are there because the description is optional and the integer may change, so a missing field should never crash the error path.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

What to log, and what to keep out of logs

A useful failure log records enough to reproduce the decision without exposing credentials. For each failed call, record:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  • the cURL error number and message, when Layer 1 failed;
  • the HTTP status code, whenever it was read;
  • whether the body decoded as JSON, plus a bounded excerpt if it did not;
  • Telegram’s error_code and description, when present;
  • the method name and your own request identifier, not the token.

Never log the full URL, because it contains the bot token. Watch for the less obvious routes too. Setting CURLOPT_VERBOSE writes request details, including the URL, to the output stream. Some cURL error strings can also echo the URL, so scrub the token from any message before it is written. A simple approach is to replace the token with a fixed marker before logging:

$safeMessage = str_replace($token, '***', $error);
error_log("cURL transport failure ($errno): $safeMessage");

Timeouts, retries, and duplicate messages

The example sets CURLOPT_TIMEOUT to 20 seconds. That value is a placeholder for your own latency budget, not a recommendation for every deployment. A timeout is a Layer 1 failure, but it does not tell you whether Telegram processed the request. The message may have been sent even though your code never received the response.

That uncertainty decides retry behavior for sendMessage. Retrying after a timeout can deliver the same text twice. Before adding retries, decide whether a duplicate is acceptable for your application, or whether you can detect one, for example by recording a local idempotency key and checking your own store before resending. Retry only the failures you have chosen to retry, and do not treat every non-2xx status or every ok failure as safe to repeat without that decision.

Whatever policy you choose, keep the Layer 2 and Layer 4 values in the log entry. They are the evidence you will need when the policy is wrong.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Troubleshooting by symptom

Symptom Layer to check first What to read
curl_exec() returns false 1: transport curl_errno() and curl_error(), read before curl_close()
Body returns, but status is not 200 2: HTTP status curl_getinfo($ch, CURLINFO_HTTP_CODE), then the JSON body for Telegram’s description
Exception about invalid JSON 3: decoding The bounded body excerpt and the HTTP status; check for proxy or HTML error pages
Valid JSON with ok set to false 4: Telegram result description, error_code, and any parameters object
Valid JSON with ok set to true, but no result 4: Telegram result Log the full decoded shape without the token; treat the response as unexpected

Following the layers in order keeps each diagnosis narrow. A transport failure tells you nothing about Telegram, and a Telegram error tells you nothing about the network. Keeping the checks separate is what lets you tell which side of the request failed.

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.

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

More from the Wire

  1. World desk4 min
    How to Spot an AI Voice Scam Before Sending MoneyDon’t rely on how a caller sounds. Pause, call back through a known number, and verify the emergency with another trusted person before sending money.
  2. Mountain View desk4 min
    Google’s SynthID Detector: How to Check AI-Generated Images, Video and AudioGoogle’s SynthID Detector looks for an embedded watermark in supported images, video and audio. Here is what its results do—and do not—show.
  3. Redmond desk20 min
    How to create a link to File or Folder in Windows 11Windows 11 gives you several ways to point to a file or folder without moving or duplicating it. You can create a desktop shortcut,…
Recommended PC Tool
Recommended PC Tool
Crashes, No Sound, or Screen Glitches?Free driver scan
Windows Errors? Fix Them Before They SpreadFree repair scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.