Building a Telegram Ping-Pong Keep-Alive Monitor with Gradio

2026, Mar 11    

πŸ€– Telegram Ping-Pong Keep-Alive Monitor

A lightweight, self-contained uptime monitoring system using two Telegram bots, a background watchdog, and a real-time Gradio web dashboard β€” all running concurrently in pure Python.


πŸ“Œ Table of Contents


Overview

When you’re running services on free-tier platforms like Render, Railway, or Hugging Face Spaces, they often spin down after inactivity. The classic workaround is a β€œkeep-alive” ping β€” a periodic HTTP or message-based signal to keep the service awake.

This project takes that idea further: instead of just sending a fire-and-forget ping, it verifies that the ping was received and responded to. It does this with a two-bot Telegram system and a watchdog that alerts you when a pong never comes back.

The entire system runs in a single Python process with three concurrent threads and exposes a live Gradio dashboard to monitor activity.


How It Works

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    Python Process                        β”‚
β”‚                                                         β”‚
β”‚  Thread 1: Ping Scheduler                               β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚  Every 5 min β†’ Send "ping" to Telegram channel   β”‚  β”‚
β”‚  β”‚  After 12s  β†’ Check if pong was received         β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚                                                         β”‚
β”‚  Thread 2: Pong Bot                                     β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚  Listens on channel β†’ Sees "ping"                β”‚  β”‚
β”‚  β”‚  Replies "pong" β†’ Records timestamp              β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚                                                         β”‚
β”‚  Thread 3: Gradio UI (Main Thread)                      β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚  Displays ping/pong logs and missed pongs        β”‚  β”‚
β”‚  β”‚  Accepts manual ping trigger via button or URL   β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚                          β”‚
         β–Ό                          β–Ό
  Telegram Channel          Public Gradio URL
  @keep_alive_ping          (share=True)

The Ping Bot sends "ping" to a public Telegram channel. The Pong Bot listens to the same channel, sees the message, and replies "pong". A watchdog timer fires 12 seconds after each ping to verify the pong arrived on time.


Project Structure

ping-pong-monitor/
β”œβ”€β”€ main.py                  # Entry point β€” starts all threads
β”œβ”€β”€ ping_scheduler.py        # Sends pings, runs watchdog
β”œβ”€β”€ pong_bot.py              # Receives pings, sends pongs
β”œβ”€β”€ gradio_interface_v2.py   # Web UI dashboard
└── README.md                # This file

Architecture Deep Dive

main.py β€” The Orchestrator

import threading
from ping_scheduler import start_ping_loop
from pong_bot import run_pong_bot
from gradio_interface import launch_ui

if __name__ == "__main__":
    threading.Thread(target=start_ping_loop, daemon=True).start()
    threading.Thread(target=run_pong_bot, daemon=True).start()
    launch_ui()

This is elegantly simple. Both background threads are marked as daemon threads, which means they automatically terminate when the main thread (the Gradio UI) exits. The UI itself runs on the main thread β€” this matters because Gradio’s server loop is blocking.

Key design decision: Using daemon=True means you don’t need explicit shutdown logic. When the user closes the app or the process ends, the bots stop cleanly.


ping_scheduler.py β€” The Sender

This module is responsible for three things:

  1. Sending periodic pings to a Telegram channel
  2. Scheduling a watchdog check 12 seconds after each ping
  3. Logging missed pongs when no response arrives in time
def send_ping():
    global last_ping
    url = f"https://api.telegram.org/bot{PING_BOT_TOKEN}/sendMessage"
    params = {"chat_id": CHAT_ID, "text": "ping"}
    res = requests.get(url, params=params)
    if res.ok:
        last_ping = datetime.now()
        ping_log.append(("ping", last_ping.strftime("%H:%M:%S")))
        if len(ping_log) > 10:
            ping_log.pop(0)
        # Schedule watchdog 12 seconds later
        threading.Timer(12, check_pong_watchdog).start()

The ping is sent using the Telegram Bot API directly over HTTP β€” no library needed for the sender side, just requests. After a successful send, a threading.Timer is created to fire check_pong_watchdog() 12 seconds later. This gives the pong bot enough time to receive and respond.

The Watchdog

def check_pong_watchdog():
    if last_ping is None or last_pong_timestamp is None:
        missed_pongs.append(...)
        return
    if last_pong_timestamp < last_ping:
        missed_pongs.append(last_ping.strftime("%H:%M:%S"))

The logic is straightforward:

  • If last_pong_timestamp is None, no pong has ever come β€” missed
  • If last_pong_timestamp < last_ping, the most recent pong is older than the most recent ping β€” missed

Both conditions result in the timestamp being logged to missed_pongs.

Ping Loop

def start_ping_loop(interval_sec=300):  # 5 minutes
    while True:
        send_ping()
        time.sleep(interval_sec)

A simple infinite loop with a configurable interval (default 5 minutes). Change interval_sec to adjust frequency.


pong_bot.py β€” The Responder

import telebot
from datetime import datetime

bot = telebot.TeleBot(PONG_BOT_TOKEN)

@bot.channel_post_handler(func=lambda msg: msg.text and msg.text.lower() == "ping")
def respond_pong(message):
    global last_pong, last_pong_timestamp
    bot.send_message(message.chat.id, "pong")
    last_pong_timestamp = datetime.now()
    last_pong = last_pong_timestamp.strftime("%H:%M:%S")
    pong_log.append(("pong", last_pong))
    if len(pong_log) > 10:
        pong_log.pop(0)

def run_pong_bot():
    bot.infinity_polling()

This bot uses pyTelegramBotAPI (telebot) for its clean decorator-based message handling. The @bot.channel_post_handler decorator means it only triggers on channel posts (not direct messages), which is exactly what we need since the ping bot posts to a channel.

The handler checks that the message is exactly "ping" (case-insensitive) and replies with "pong" to the same chat. Timestamps and logs are updated in globals, which the watchdog and UI read from.

Why two separate bots? Telegram bots cannot read their own messages. If we used one bot to send the ping, it couldn’t also detect it to send the pong. Two separate bot tokens solve this cleanly.


gradio_interface_v2.py β€” The Dashboard

The UI is built with Gradio, which lets you create web interfaces in pure Python with minimal boilerplate.

MAX_LOG_ENTRIES = 20

def get_logs():
    pings = get_ping_log()[-MAX_LOG_ENTRIES:]
    pongs = get_pong_log()[-MAX_LOG_ENTRIES:]
    missed = get_missed_pongs()
    # ... formats into a readable text block

The get_logs() function pulls from all three data sources and formats them into a single markdown-style text block. Entries are capped at 20 to keep the display clean.

Layout

with gr.Blocks() as ui:
    gr.Markdown("# πŸ€– Telegram Ping Pong Monitor with Watchdog")
    with gr.Row():
        refresh_btn = gr.Button("πŸ” Refresh Logs")
        ping_btn = gr.Button("πŸ“€ Send Ping Now")
    output = gr.Textbox(lines=20, interactive=False, label="Ping-Pong Logs + Watchdog")
    refresh_btn.click(get_logs, outputs=output)
    ping_btn.click(manual_ping, outputs=output)
    ui.load(fn=on_load, inputs=None, outputs=output)

Two buttons: one to refresh the log view, one to manually fire a ping. The ui.load handler runs on page load, enabling URL-based triggering.

Public Sharing

def launch_ui():
    ui.launch(share=True)

share=True generates a public gradio.live URL β€” no deployment required. This is perfect for sharing the dashboard with teammates or embedding in external monitoring tools.


Setup & Installation

Prerequisites

  • Python 3.8+
  • Two Telegram bot tokens (create via @BotFather)
  • A public Telegram channel

Install Dependencies

pip install requests pyTelegramBotAPI gradio

Create Your Telegram Setup

  1. Go to @BotFather on Telegram
  2. Create Bot 1 (the Ping Bot) β€” copy its token
  3. Create Bot 2 (the Pong Bot) β€” copy its token
  4. Create a public Telegram channel (e.g. @your_keep_alive_channel)
  5. Add both bots as administrators to the channel

Configuration

Open ping_scheduler.py and pong_bot.py and update the tokens and channel:

ping_scheduler.py

PING_BOT_TOKEN = "YOUR_PING_BOT_TOKEN_HERE"
CHAT_ID = "@your_channel_username"

pong_bot.py

PONG_BOT_TOKEN = "YOUR_PONG_BOT_TOKEN_HERE"

Security tip: For production use, store tokens in environment variables and load them with os.environ.get("PING_BOT_TOKEN") instead of hardcoding.


Running the Monitor

python main.py

On startup you’ll see:

  • The ping loop begins (first ping fires immediately)
  • The pong bot starts polling Telegram
  • Gradio launches and prints a local URL (and a public gradio.live URL if share=True)

Open the URL in your browser to see the live dashboard.


Dashboard Features

Feature Description
🟒 Ping Log Last 20 pings with timestamps
πŸ”΅ Pong Log Last 20 pongs with timestamps
⚠️ Missed Pongs Up to 5 most recent missed pong events
πŸ“ˆ Last Ping Timestamp of the most recent ping sent
πŸ“ˆ Last Pong Timestamp of the most recent pong received
πŸ” Refresh Button Manually refresh the log view
πŸ“€ Send Ping Button Immediately fire a manual ping

URL-Triggered Pings

One of the more powerful features is the ability to trigger a ping via a URL query parameter. This enables integration with external schedulers like UptimeRobot, cron jobs, or CI/CD pipelines.

def on_load(request: gr.Request):
    query = request.query_params or {}
    if query.get("ping", "false").lower() == "true":
        return manual_ping()
    return get_logs()

To trigger a ping externally, simply visit:

https://your-gradio-url.gradio.live/?ping=true

Use cases:

  • Set UptimeRobot to hit ?ping=true every 5 minutes
  • Add it as a cron webhook to keep Render services alive
  • Use it in GitHub Actions as a scheduled health check step

Watchdog Logic

The watchdog is the heart of the monitoring system. It runs 12 seconds after every ping and checks two conditions:

Ping sent at T
           β”‚
           β–Ό
      T + 12 seconds
           β”‚
           β”œβ”€ last_pong_timestamp is None?  β†’ MISSED (never received any pong)
           β”‚
           └─ last_pong_timestamp < last_ping?  β†’ MISSED (pong is stale)
                                                   (no new pong came after this ping)

If either condition is true, the ping’s timestamp is appended to the missed_pongs list. The UI shows the last 5 missed pong events at a glance.

The 12-second window is intentional β€” it accounts for Telegram API latency, pyTelegramBotAPI polling intervals, and processing time, while still being fast enough to detect genuine failures.


Download Source Files

You can download all source files as a zip archive here:

πŸ“¦ Download ping-pong-monitor.zip

The archive includes:

  • main.py
  • ping_scheduler.py
  • pong_bot.py
  • gradio_interface_v2.py

Built with Python, Gradio, and the Telegram Bot API.