Using AWS Lambda as Telegram bot backend

Rralf
Serverless Bots
Published in
7 min readJun 22, 2024

From Idea to Implementation: Creating and Configuring a Telegram Bot Using AWS Lambda for Fast, Flexible, Serverless, and Cost-Effective Interaction.

Telegram bots can operate in two modes:

  1. Pull Mode — In this mode, our bot continuously runs and polls Telegram to check if there are any new updates. If there are new messages, it reacts to them accordingly.
  2. Webhook Mode — In this mode, Telegram sends an HTTP request to a specified URL whenever there is a new message. The bot then processes these incoming requests.

In pull mode, we need to have a continuously running code that polls Telegram. This is a secure solution because we don’t need to handle incoming connections, but it requires hosting the code somewhere, which can be inconvenient.

In webhook mode, we face a similar hosting issue — we need to have a resource where Telegram can send its requests.

Why not to host the webhook API on AWS? Serverless infrastructure is an excellent fit for this type of solution. Here’s a simple way to set this up:

sample telegram to lambda integration

To implement this, we need to:

  1. Register a Telegram bot.
  2. Create a lambda function that will be called by Telegram and publish this function to the “world” via REST API.
  3. Register the HTTP API URL as a webhook, thus allowing Telegram to call our lambda.
  4. Learn how to send responses to Telegram requests via the Telegram HTTP API.

Let’s go!

Registering a Bot

There are plenty of instructions on how to do this online. In short, we need to contact @BotFather and use the /newbot command to create a new bot. As a result of the registration, we will receive a Bot Token (a certain code that allows us to identify our bot).

Creating a Lambda Function

For further work, you need to have access to an AWS account. If you don’t have one yet, you can create an account for yourself here.

In the AWS Console, go to the Lambda section and create a new function. For example, we will use Python. But you can replace Python with any other language that is convenient for you.

In the section for creating a new function, select the Python blueprint and specify the function name:

After creating the function, we’ll change the code to the following:

import json

def lambda_handler(event, context):
print("*** Received event")
print(json.dumps(event))

return "Ok" # Echo back the first key value

At this stage, the placeholder for the lambda function is ready. So we can proceed with creating the API.

Creating an HTTP API for Lambda

To create an API, go to the API Gateway section and create a new HTTP API.

When creating the API, define the integration with our lambda function and specify API name:

On the next page, we’ll change the resource path.

After the API created, let’s check its accessibility in the browser. The API URL can be found in the “Stages” section (copy the URL from there).

Let’s try to open the link. Don’t forget to add /webhook path to the Invoke URL:

The browser shows an error about the incorrect JSON response, but it’s not a problem. The most important that we’ve seen the “Ok” response. That means that the overall infrastructure for our lambda function is ready to work.

Now we can start modifying the lambda function itself to handle Telegram requests.

Lambda function tuning

The Telegram requests format we can find in the documentation.

As an argument, our lambda function will receive a message similar to the following:

{
"update_id": 10000,
"message": {
"date": 1441645532,
"chat": {
"last_name": "Test Lastname",
"id": 1111111,
"first_name": "Test",
"username": "Test"
},
"message_id": 1365,
"from": {
"last_name": "Test Lastname",
"id": 1111111,
"first_name": "Test",
"username": "Test"
},
"text": "/start"
}
}

At this stage, we are interested in the following fields:

  • $.message.chat.id - this is the chat ID within which Telegram users wrote something to our bot.
  • $.message.from.username - this is the Telegram username of the user who sent us the message.
  • $.message.text - this is the text of the user's message.

Let’s try to log these fields in our lambda’s operation. To do this, we’ll replace the function code with the following:

import json

def lambda_handler(event, context):
print("*** Received event")
print(json.dumps(event))

chat_id = event['message']['chat']['id']
user_name = event['message']['from']['username']
message_text = event['message']['text']

print(f"*** chat id: {chat_id}")
print(f"*** user name: {user_name}")
print(f"*** message text: {message_text}")


return "Ok" # Echo back the first key value

Additionally, for testing, we’ll create a new test event that will be passed to the lambda function. Since the lambda is integrated with the HTTP API, it will receive messages similar to the following:

{
"version": "2.0",
"routeKey": "ANY /webhook",
"rawPath": "/webhook",
"rawQueryString": "",
"headers": {
"accept": "*/*",
"accept-encoding": "gzip, deflate, br",
"content-length": "394",
"content-type": "application/json",
"host": "ng79a68gph.execute-api.eu-central-1.amazonaws.com",
"postman-token": "cbdf81be-3707-4383-b5b4-0d6fc71940e6",
"user-agent": "PostmanRuntime/7.39.0",
"x-amzn-trace-id": "Root=1-6676d931-3d6752cf6f055cd810e8f4e2",
"x-forwarded-for": "185.244.156.97",
"x-forwarded-port": "443",
"x-forwarded-proto": "https"
},
"requestContext": {
"accountId": "126459111222",
"apiId": "ng79a68gph",
"domainName": "ng79a68gph.execute-api.eu-central-1.amazonaws.com",
"domainPrefix": "ng79a68gph",
"http": {
"method": "POST",
"path": "/webhook",
"protocol": "HTTP/1.1",
"sourceIp": "185.244.156.97",
"userAgent": "PostmanRuntime/7.39.0"
},
"requestId": "Zxbfujs1liAEJZQ=",
"routeKey": "ANY /webhook",
"stage": "$default",
"time": "22/Jun/2024:14:01:21 +0000",
"timeEpoch": 1719064881172
},
"body": "{\r\n \"update_id\": 10000,\r\n \"message\": {\r\n \"date\": 1441645532,\r\n \"chat\": {\r\n \"last_name\": \"Test Lastname\",\r\n \"id\": 1111111,\r\n \"first_name\": \"Test\",\r\n \"username\": \"Test\"\r\n },\r\n \"message_id\": 1365,\r\n \"from\": {\r\n \"last_name\": \"Test Lastname\",\r\n \"id\": 1111111,\r\n \"first_name\": \"Test\",\r\n \"username\": \"Test\"\r\n },\r\n \"text\": \"/start\"\r\n }\r\n}",
"isBase64Encoded": false
}

Let’s set this message as a test example and see what we get:

Great! We managed to read the required fields from the message!

Our function is now ready to receive messages from Telegram. Next, we need to learn how to respond. For that, we will use the sendMessage method of the Telegram API.

Let’s add a method to our lambda function that will send replay messages to the specified chat_id. Once again, update the function code. Don't forget to replace "your-bot-token" with the token of your Telegram bot, which was returned by @BotFather.

import json
import urllib3

BOT_TOKEN="your-bot-token"


def sendReply(chat_id, message):
reply = {
"chat_id": chat_id,
"text": message
}

http = urllib3.PoolManager()
url = f"https://api.telegram.org/bot{BOT_TOKEN}/sendMessage"
encoded_data = json.dumps(reply).encode('utf-8')
http.request('POST', url, body=encoded_data, headers={'Content-Type': 'application/json'})

print(f"*** Reply : {encoded_data}")


def lambda_handler(event, context):
body = json.loads(event['body'])

print("*** Received event")

chat_id = body['message']['chat']['id']
user_name = body['message']['from']['username']
message_text = body['message']['text']

print(f"*** chat id: {chat_id}")
print(f"*** user name: {user_name}")
print(f"*** message text: {message_text}")
print(json.dumps(body))

reply_message = f"Reply to {message_text}"

sendReply(chat_id, reply_message)

return {
'statusCode': 200,
'body': json.dumps('Message processed successfully')
}

As a result, our function should be able to reply something in Telegram. The response should be the string “Reply to” followed by the user’s message. This behavior will have to be changed later, but for now, it is sufficient now to build the whole webhook bot infrastructure.

Let’s connect the bot to Telegram.

Connecting to Telegram

To connect to Telegram, you need to make a request to the following address:

https://api.telegram.org/bot{bot_token}/setWebhook?url={webhook_endpoint}

where:

  • {bot_token} is the bot token we already used above.
  • {webhook_endpoint} is the URL of our API.

The response after setting the webhook URL:

{
"ok": true,
"result": true,
"description": "Webhook was set"
}

To verify the connection, you can make a request to:

https://api.telegram.org/bot{bot_token}/getWebhookInfo

The response should look something like this:

{
"ok": true,
"result": {
"url": "{webhook_endpoint}",
"has_custom_certificate": false,
"pending_update_count": 0,
"max_connections": 40,
"ip_address": "..."
}
}

Let’s try to send some messages to our bot:

It works!

You can observe the behavior of our lambda function in its operation logs in CloudWatch:

Furthermore, CloudWatch will be very useful as a debugging tool in case the Lambda function is not performing as expected or if the bot is not responding in Telegram. Please note that messages in CloudWatch appear with a slight delay (1 to 3 minutes).

--

--