Plans from ₹99 a monthFree HTTPS on every site18% GST invoice with your GSTIN for input tax creditFull refund within 7 days of your first paymentPay yearly, get 2 months freeNo auto-renewal, everPlans from ₹99 a monthFree HTTPS on every site18% GST invoice with your GSTIN for input tax creditFull refund within 7 days of your first paymentPay yearly, get 2 months freeNo auto-renewal, ever

Python apps

Example: a Telegram bot worker

Run a Telegram bot all day as a worker that polls for messages, restarts itself after errors and logs what it does.

Intermediate25 min readUpdated 27 October 2026
On this page

In this tutorial you build acme-bot, a Telegram bot that answers customers' questions: /start, /hours and /price <item>. It runs as a worker, so it stays on all day.

Telegram can deliver messages to a bot in two ways: by calling your server (a webhook) or by your bot asking for new messages (long polling). FiniteHost apps cannot receive incoming connections, so this bot uses long polling: it keeps one HTTPS request open to Telegram and gets new messages the moment they arrive. That is just as fast for a small bot.

Before you start#

  • A plan with a free Python app slot (Pro and above, or any Compute plan).
  • A bot token from @BotFather. Steps 1 and 2 of the price alert example show how.
  • If the same bot used a webhook elsewhere, remove it: open https://api.telegram.org/bot<TOKEN>/deleteWebhook once. Telegram does not allow polling while a webhook is set.

Step 1: Write the bot#

Make a folder acme-bot with three files.

requirements.txt

Text
requests==2.32.3

config.json

JSON
{
  "telegram_token": "123456789:AAH-your-token",
  "hours": "Mon-Sat, 10:00 to 19:00 IST",
  "prices": {"drill": 2499, "saw": 5890}
}

main.py

Python
"""acme-bot: a Telegram bot that runs as a FiniteHost worker (long polling, no webhook)."""
import json
import logging
import time
from pathlib import Path

import requests

HERE = Path(__file__).resolve().parent
CONFIG = json.loads((HERE / "config.json").read_text(encoding="utf-8"))
API = f"https://api.telegram.org/bot{CONFIG['telegram_token']}"
OFFSET_FILE = HERE / "offset.txt"     # remembers the last message handled, across restarts

logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
log = logging.getLogger("acme-bot")
session = requests.Session()


def call(method: str, **params) -> dict:
    # Long poll: Telegram holds the request open for up to 50 s; we wait a little longer.
    r = session.post(f"{API}/{method}", data=params, timeout=60)
    r.raise_for_status()
    body = r.json()
    if not body.get("ok"):
        raise RuntimeError(f"{method} failed: {body}")
    return body["result"]


def reply_to(text: str) -> str:
    words = text.strip().split()
    command = words[0].split("@")[0].lower() if words else ""
    if command == "/start":
        return "Hello! Try /hours or /price drill."
    if command == "/hours":
        return f"We are open {CONFIG['hours']}."
    if command == "/price":
        if len(words) < 2:
            return "Which item? For example: /price drill"
        item = words[1].lower()
        price = CONFIG["prices"].get(item)
        return f"{item}: Rs {price}" if price is not None else f"Sorry, no price for {item}."
    return "I understand /hours and /price <item>."


def load_offset() -> int:
    try:
        return int(OFFSET_FILE.read_text().strip())
    except (FileNotFoundError, ValueError):
        return 0


def main() -> None:
    offset = load_offset()
    delay = 1
    log.info("bot started, offset %s", offset)
    while True:
        try:
            updates = call("getUpdates", offset=offset, timeout=50)
            delay = 1                                   # a good call resets the back-off
            for u in updates:
                offset = u["update_id"] + 1
                msg = u.get("message") or {}
                text = msg.get("text")
                if text:
                    answer = reply_to(text)
                    call("sendMessage", chat_id=msg["chat"]["id"], text=answer)
                    log.info("answered %r", text[:40])
                OFFSET_FILE.write_text(str(offset))
        except (requests.RequestException, RuntimeError) as e:
            log.warning("telegram error: %s; retrying in %s s", e, delay)
            time.sleep(delay)
            delay = min(delay * 2, 60)              # back off up to one minute


if __name__ == "__main__":
    main()

What makes it robust:

  • Timeouts on every request, so a dead connection never hangs the bot.
  • Back-off: after an error it waits 1, 2, 4 … up to 60 seconds before trying again, instead of hammering Telegram.
  • Offset file: after a restart it continues where it stopped and never answers a message twice.
  • Logging with timestamps, visible in the panel.

Step 2: Create the worker#

  1. Open Python apps and go to Create an app.
  2. Enter App name acme-bot, keep Entry file main.py.
  3. Choose Mode: Worker: keeps running. The Schedule list disappears; workers do not take one.
  4. Press Create app.

Step 3: Upload and start#

  1. Zip the acme-bot folder and drop the zip on the Upload code box of the app's card. Wait about a minute.
  2. Press Start. The panel says "Starting. It runs within 30 seconds."
  3. Press refresh after half a minute and open Logs. You should see Installing requirements.txt (first start only) and bot started, offset 0.
  4. Message your bot /hours in Telegram. The reply arrives in a second or two, and the log shows answered '/hours'.

Updating the bot#

  1. Upload the new code (zip or SFTP).
  2. Press Stop, then Start. A running worker keeps the old code in memory until it restarts.

To change prices or hours, edit config.json, upload it, and restart the same way.

How restarts work#

  • If the bot crashes (an uncaught exception), FiniteHost starts it again after 5 seconds, waiting longer after repeated crashes, up to 5 minutes.
  • If it crashes 10 times within 10 minutes, it is left stopped so a broken bot does not loop forever. Fix the error in the logs, upload, and press Start.
  • Server maintenance restarts the worker automatically; the offset file means no messages are lost or repeated.
  • If your plan is suspended for non-payment, the worker stops and starts again when you renew.

Troubleshooting tips#

  • 409 Conflict: terminated by other getUpdates request: the same token runs somewhere else too (your laptop?). Stop the other copy.
  • 409 Conflict: can't use getUpdates method while webhook is active: open the deleteWebhook link from Before you start.
  • 401 Unauthorized: wrong token in config.json.
  • The bot stopped answering and the card says failed: it crashed 10 times. Read Logs, fix, press Start.

What's next#

Was this helpful?

Still stuck?

Email support@finitehost.com with your subscription ID (it starts with t, shown on the panel's Dashboard) and what you tried. A person replies, usually within one business day.

Last updated 27 October 2026. Found something wrong? Tell support@finitehost.com.