$chat_id, 'text' => $text, 'parse_mode' => 'HTML', 'disable_web_page_preview' => true, ]; if ($reply_markup !== null) { $body['reply_markup'] = wp_json_encode($reply_markup); } $url = 'https://api.telegram.org/bot' . $token . '/sendMessage'; $response = wp_remote_post($url, [ 'timeout' => 10, 'body' => $body, ]); if (is_wp_error($response)) { throw new TelegramException('Network: ' . $response->get_error_message(), 0, false); } $code = (int)wp_remote_retrieve_response_code($response); $raw = (string)wp_remote_retrieve_body($response); $data = json_decode($raw, true); if ($code === 200 && is_array($data) && !empty($data['ok'])) { return $data; } $desc = is_array($data) ? (string)($data['description'] ?? '') : ''; $retry_after = 0; if (is_array($data) && isset($data['parameters']['retry_after'])) { $retry_after = (int)$data['parameters']['retry_after']; } $permanent = self::is_permanent($code, $desc); throw new TelegramException( sprintf('HTTP %d %s', $code, $desc !== '' ? $desc : 'unknown error'), $code, $permanent, $retry_after ); } private static function is_permanent(int $code, string $desc): bool { // 4xx (except 429 rate-limit) are client-side errors: bad request, // invalid chat, revoked token, malformed payload — retrying will not // help. Mark as permanent so the queue gives up immediately and the // admin is notified via the dashboard widget. if ($code >= 400 && $code < 500 && $code !== 429) return true; return false; } }