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.
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>/deleteWebhookonce. 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
requests==2.32.3config.json
{
"telegram_token": "123456789:AAH-your-token",
"hours": "Mon-Sat, 10:00 to 19:00 IST",
"prices": {"drill": 2499, "saw": 5890}
}main.py
"""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#
- Open Python apps and go to Create an app.
- Enter App name
acme-bot, keep Entry filemain.py. - Choose Mode: Worker: keeps running. The Schedule list disappears; workers do not take one.
- Press Create app.
Step 3: Upload and start#
- Zip the acme-bot folder and drop the zip on the Upload code box of the app's card. Wait about a minute.
- Press Start. The panel says "Starting. It runs within 30 seconds."
- Press refresh after half a minute and open Logs. You should see
Installing requirements.txt(first start only) andbot started, offset 0. - Message your bot
/hoursin Telegram. The reply arrives in a second or two, and the log showsanswered '/hours'.
Updating the bot#
- Upload the new code (zip or SFTP).
- 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 thedeleteWebhooklink from Before you start.401 Unauthorized: wrong token inconfig.json.- The bot stopped answering and the card says failed: it crashed 10 times. Read Logs, fix, press Start.
- More in Fix a failing Python app.
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.