Getting your python telegram bot working on Render (2024)

Does "telegram.error.Conflict: Conflict: terminated by other getUpdates request"; make sure that only one bot instance is running sound familiar? If you’re trying to host your python telegram bot on Render, you’ve probably seen this error.

Telegram bots get messages in one of two ways: through polling (done via getUpdates) or through webhooks. If your bot script is using application.run_polling(...), then you’re using getUpdates.

Render’s web service infrastructure is constantly probing your services using the health check to see if they’re healthy. If the health check times out, returns nothing, or returns a negative response, Render will deploy another instance to attempt to replace the unhealthy one. For web services, Render’s health checks only come in the form of an HTTP request, so you need something in your running service that can handle that reequest.

Most Telegram bots that use the run_polling method are not attached to web servers, and can’t handle the request from Render’s health check. Without a mechanism to tell render that your service is healthy, even though your service is doing what it’s supposed to, Render will think it’s unhealthy and deploy a new instance of it. And that’s when the error begins: you now have 2 instances of your bot running, and the getUpdates API doesn’t accept that and emits the Conflict error.

How do we get around this?

Paid option: Background workers

If you’re open to paying, background workers are actually a better solution for this problem. Render’s background workers are designed to run long-running processes, and they don’t have health checks. So you can just drop in your bot on a background worker and not have to worry about the health check causing Render to create new instances.

Free: Switch to webhooks

We can still use Render to host our bot, but we have to switch to using webhooks to survive the health check. The python-telegram-bot example library has an example webhook implementation bot we can simplify and adapt to be an echobot, and just deploy with the python runtime so no Docker image is needed:

import asyncioimport loggingimport osimport uvicornfrom starlette.applications import Starlettefrom starlette.requests import Requestfrom starlette.responses import PlainTextResponse, Responsefrom starlette.routing import Routefrom telegram import Updatefrom telegram.ext import ( Application, ContextTypes, filters, MessageHandler,)# Enable logginglogging.basicConfig( format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO)# set higher logging level for httpx to avoid all GET and POST requests being loggedlogging.getLogger("httpx").setLevel(logging.WARNING)logger = logging.getLogger(__name__)# Define configuration constantsURL = os.environ.get("RENDER_EXTERNAL_URL") # NOTE: We need to tell telegram where to reach our servicePORT = 8000TOKEN = "<YOUR TOKEN>"" # nosec B105async def echo(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: """Echo the user message.""" await update.message.reply_text(update.message.text)async def main() -> None: """Set up PTB application and a web application for handling the incoming requests.""" application = ( Application.builder().token(TOKEN).updater(None).build() ) # register handlers application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, echo)) # Pass webhook settings to telegram await application.bot.set_webhook(url=f"{URL}/telegram", allowed_updates=Update.ALL_TYPES) # Set up webserver async def telegram(request: Request) -> Response: """Handle incoming Telegram updates by putting them into the `update_queue`""" await application.update_queue.put( Update.de_json(data=await request.json(), bot=application.bot) ) return Response() async def health(_: Request) -> PlainTextResponse: """For the health endpoint, reply with a simple plain text message.""" return PlainTextResponse(content="The bot is still running fine :)") starlette_app = Starlette( routes=[ Route("/telegram", telegram, methods=["POST"]), Route("/healthcheck", health, methods=["GET"]), ] ) webserver = uvicorn.Server( config=uvicorn.Config( app=starlette_app, port=PORT, use_colors=False, host="0.0.0.0", # NOTE: Render requires you to bind your webserver to 0.0.0.0 ) ) # Run application and webserver together async with application: await application.start() await webserver.serve() await application.stop()if __name__ == "__main__": asyncio.run(main())

Some things to note here:

  1. URL = os.environ.get("RENDER_EXTERNAL_URL"). Because we receive notifications via webhooks, we have to tell Render where to find our service. All web services are given public URLs that end with .onrender.com by Render, and that URL is actually available to us as an environment variable. If you’re using your own domain, you can use that instead.

  2. host="0.0.0.0". Render needs us to bind our webserver to all ip addresses for a machine, so the loopback address (127.0.0.1) shouldn’t be used.

Remember the Render free tier will put your service to sleep after 15 minutes of inactivity, so without activity from Telegram, you’ll need to ping your service to keep it awake. If you want to prevent it from sleeping, you can use my free keepalive tool to keep it awake and responsive.

Getting your python telegram bot working on Render (2024)
Top Articles
Latest Posts
Article information

Author: Gregorio Kreiger

Last Updated:

Views: 6218

Rating: 4.7 / 5 (57 voted)

Reviews: 88% of readers found this page helpful

Author information

Name: Gregorio Kreiger

Birthday: 1994-12-18

Address: 89212 Tracey Ramp, Sunside, MT 08453-0951

Phone: +9014805370218

Job: Customer Designer

Hobby: Mountain biking, Orienteering, Hiking, Sewing, Backpacking, Mushroom hunting, Backpacking

Introduction: My name is Gregorio Kreiger, I am a tender, brainy, enthusiastic, combative, agreeable, gentle, gentle person who loves writing and wants to share my knowledge and understanding with you.