Skip to Content
Technical Articles

How to broadcast Telegram messages via SAP CPI and Telegram Bots

In today’s article we want to deal with the integration of the telegram messenger via SAP CPI. But why telegram? In the list of the most popular messengers, telegram occupies “only” 6th place and of course, WhatsApp is by far the most popular messenger, but unlike WhatsApp, telegram has one major advantage: It offers an open, free and easy to use API. (Whereas access to Whatsapp’s api is limited and chargeable.)

In the following article we want to create a Telegram Bot as well as an IFlow, serving as backend, which is able to send messages in broadcast-style to all users registered in the bot. Since I do not assume that everyone has already gained experience with Telegram Bots, I also go into the creation of Telegram bots. (If you already know this, just skip the first sections.) Before we start, let’s take a quick look at the table of contents.

Table of contents

  • Which services are needed – the target image
  • How to create a Telegram bot – Getting to know the botfather
  • Setting up the IFlow
    • IFlow Pt. 1 – How to connect a Telegram bot with CPI via Webhooks
    • IFlow Pt. 2 – Setting up the registration logic
    • IFlow Pt. 3 – Setting up the broadcast logic
  • Testing – let’s check the broadcast functionality
  • Ideas and areas for improvement
  • Conclusion

Which services are needed – the target image

Before we dive into the hands-on part, let’s stop for a second and think about the target image. Which functions should our target solution serve? We want to build a SAP CPI interface that allows us to send message to all users of a Telegram bot. Therefore we need to know who is a user of the bot. Since we’re like the early Google (“[…] don’t be evil […]”), we assume that may want to stop receiving messages. Thus we need also a function which enables users to unsubscribe from our broad cast messages.

So our first functionality will enable the user to subscribe to our bot via a specific Telegram message. Telegram will then push the user’s message to our CPI tenant which takes the user id, stores it and sends back an approval message to the user.

The next use case will be the unsubscribe functionality. If an user sends a specific message (e.g. “/stop”) we will delete the user’s id from our datastore and send back an approval message.

The third functionality looks a little bit different. This use case is triggered by a thirdparty (user or system). If the thirdparty sends in a text message, the CPI looksup for all user ids and sends the message to each single user. That’s what I call “broadcast”. (An no, unfortunately there isn’t a method to just send one message with all receiver user ids to the Telegram servers. We have to send a message for each and every user.)

Now that it’s clear what our target setup looks like, let’s start creating the bot…

How to create a Telegram bot – Getting to know the botfather

The bot creation for Telegram is really straightforward. No E-Mail address, API account registration, etc. is necessary. The complete setup process can be done within the Telegram messenger itself. This results in the first and only entrance barrier: you need a telegram account. If you do not have one yet, now is the time to download the Telegram app (Android | iOS) and create an account.

For reasons of comfort, I recommend the web variant of Telegram for the following steps, which is accessible via https://web.telegram.org. This makes it easy to do the bot setup from your PC.

To create a bot, let’s chat to @botfather. This “father of all bots” is itself a bot that allows you to manage your own telegram bots. When chatting for the first time, the botfather will welcome you with a list of supported commands. Don’t be scared, we don’t need all of them. The following screenshot (click to enlarge!) will show you the most important steps.

(1) By sending /newbot we are initiating the bot setup process.

(2) At first we have to give the screenname of the bot. (That’s the name which is shown in the user’s contact list, but not the handle/id which will be used to invite/chat to the bot. So use a long descriptive name here.)

(3) Now we have to choose the bot handle. It’s the unique id/username of the bot. Users have to enter the id in the contact search. So choose a short and catchy handle.

(4) That’s it. The bot was successfully created. Copy and save the bot api token (the one in red letters) in a secure space. (And never share it. It’s the key to control your bot via API.)

The basics are done. Now let’s do some “makeup” for your future bot users. At first let’s setup a picture for our bot. Therefore initiate the picture setup process via sending /setuserpic to @botfather.

(1) Write /setuserpic to trigger the user picture setup process.

(2) Enter your bot’s handle, so that the botfathers knows which bot to maintain.

(3) Upload the user picture via the image upload function. For my bot I searched on iconfinder.com (with filter options “No link back” to find a free-to-use icon. But since we’re nice: Thanks, PixelPirate, for this cool icon.)

To guide our users we can setup predefined commands. Those will be shown to users of our bot as suggestions, so that they know how to interact with our bot. I think this is a good thing, so let’s set them up.

Initiate the process by writing /setcommands to the botfather. Then again send your bot handle. After this you can send in a list of supported commands of your bot including a description. Looking back to the use cases we defined in the first chapter, we define a /start and a /stop command. (Why didn’t I choose /subscribe and /unsubscribe? Because /start is a standard one, which will shown by telegram to the bot users anyway. If we re-use it for our purpose, we don’t have to implement an additional functionality for the /start command…)

Try to add our new bot to your contact list. You should recognize the userpic we set up before. When trying to chat to our new bot, you should recognize the suggested standard commands we set up earlier. That’s it for the bot creation. Now let’s head over to the SAP CPI implementation.

Setting up the IFlow

Everything we build today will be developed within a single IFlow. Nevertheless, for sake of clarity I splitted the IFlow related content of this article into three parts.

Open your CPI tenant, choose a package for the IFlow (or create a new one) and add a new IFlow to it. Then open the IFlow editor and follow the next steps.

IFlow Pt. 1 – How to connect a Telegram bot with CPI via Webhooks

There are two options to receive messages from your Telegram bots. A pull- and push-based. Pulling may sense, if you plan to receive dozens of messages. In that case you could keep the amount of requests low by pulling multiple messages at once. The downside? It’s not that fast like getting each and every message pushed to your CPI tenant and if you expect to get for example less than one message per 2 seconds, but want to keep your interface responsive (=you have to pull each 2 seconds) than you would make a lot of useless calls.

The second option, getting messages (proactively) pushed from the Telegram servers to your CPI tenant is a good choice, if you want to build a high responsive interface and don’t expect that much message per minute. In “push” mode each and every message a user sends to your bot is pushed via a so called “Webhook” to your CPI interface.If your want to know more about both method, you should read this.

Having a look at our use-case list, we can see that users regularly only interact once (maximum twice, if they unsubscribe) with out bot. Thus we assume that we don’t receive that much messages and therefore choose the webhook-based push scenario. To enable Telegram to push messages to us, we have to provide an endpoint. So let’s start with setting up the endpoint for Telegram.

Connect the sender via HTTS-channel with the start message element. Configure the sender channel as shown in the screenshot above. (Of course you can choose a different address name.) Then add a Groovy script element and connect the start message element with the script and the script with the end message element.

import com.sap.gateway.ip.core.customdev.util.Message;
import groovy.json.*;
import java.util.HashMap;

def Message processData(Message message) {
    //Read body as String (Parser in general would be the better choice, but 
    //since we want to reuse the body multiple times, a String is easier here.)
    def body = message.getBody(java.lang.String) as String;
    
    //Parse Telegram message from body
    def slurper = new JsonSlurper()
    def bodyObj = slurper.parseText(body)
    //Read chat.id (=user id of the Telegram user)
    message.setProperty("msgInChatId", bodyObj.message.chat.id)
    //Read chat message the user send to the bot
    message.setProperty("msgInText", bodyObj.message.text != null ? bodyObj.message.text : "")

    //Get LogFactory and create MPL attachment with original Telegram messageLog
    //This is not necessary, but really gives some nice insights
    def messageLog = messageLogFactory.getMessageLog(message);
    if(messageLog != null){
        messageLog.addAttachmentAsString("TELEGRAM_EVENT", body, "application/json");
    }
    
    return message;
}

The script will be used for two purposes:

  1. Extract the user id and the message/text from the incoming Telegram message
  2. Log the incoming Telegram event/message to the CPI message processing log for analysis purposes.

Now save and deploy your IFlow and switch to CPI’s monitoring view. Once there, open the “Manage Integration Content” view, search for your IFlow and copy the runtime url.

Now we have to marry Telegram’s botfather with your new CPI interface. This can be done via a simple HTTP/GET-request from any webclient. (I have chosen Postman for this blog.) To do so, build the following url (by replacing the {patterns} with actual values):

https://api.telegram.org/bot{telegram_api_token}/setWebhook?url=https://{cpi_user}:{cpi_pass}@{iflow_endpoint}

The {telegram_api_token} is the one you received from the botfather when initially setting up your bot. The {cpi_user} and {cpi_pass} are user credentials of a user which is able to access your interface. (I know, it seems like bad practice, but Telegram doesn’t support another way of accessing secured endpoints right now…). The {iflow_endpoint} is the one marked up in the screenshot above, but without the trailing “https://” part.

If you call this url, you should receive an answer from Telegram that you successfully registered your webhook endpoint. From now on every Telegram message to your bot should be pushed straightforward to your interface.

Note (1): For security reasons you should create a new user for use in the Webhook url. In addition it’s a good idea to change the sender role in the HTTPS sender channel from ESBMessaging.send to a custom one. (Read here how to generate custom roles.) Thus you can isolate the user in the best possible way. (And you should do so, because passing user credentials plaintext inside an url as shown above isn’t good practice. But as I told you – for this use-case there’s no other option.) Thanks to Ariel Bravo Ayala for pointing this out and for sharing his ideas!

Note (2): If you want to stop Telegram sending messages to your interface, you can “divorce” the bond between CPI and Telegram by simply calling the following url:

https://api.telegram.org/bot{telegram_api_token}/deleteWebhook

Before we start with the second part, let’s do a quick check if everything works so far. Open the telegram messenger, add your bot and send any message to it. You should find it in message log almost immediately.


(Click to enlarge.)

As you can see, the message arrived immediately and since we logged the incoming Telegram event, we can identify the text we send to the bot inside the JSON message received via webhook. Now we’re good to go for the business logic of our IFlow.

IFlow Pt. 2 – Setting up the registration logic

Let’s deal with the business logic now. As you might know from my other blogs, I prefer to start with an overview of what we will build. Below the following screenshot you will then get detailed information for the single steps.

The green block is what you’ve reached so far. Let’s start with adding the sub-processes. Add a Local Integration Process (1) and name it “Unregister user”. This will contain the logic to unregister a user (=forget his user id). Therefore add an Data Store Operation/Delete element (2) and configure it as follows.

The entry id will be filled with the user’s id, we picked up in the script step (check code listing above).

Since we want to confirm that the de-registration was successful, we will prepare a response for the chat user. Add a Content Modifier (3) and set its body tab (in Expression mode) with the following content:

{
	"chat_id": ${property.msgInChatId}, 
	"text": "Thanks for using the Telegram bot. We would love to hear from you at another time!"
}

That’s it for the “Unregister user” process. Now let’s head over to the “Register user” subprocess. Start by adding a new Local Integration Process (4) and name it “Register user”. Add a Content Modifier (5) and and set its body tab (in Expression mode) with the following content:

<chatId>${property.msgInChatId}</chatId>

That’s the content we like to store in our “user database” with all the active ids that should be informed in case of a broadcast. Next we have to store this piece of information. Therefore add a Data Store Operation/Write element (6) to the Local Integration Process and configure it as shown in the next screenshot.

As last step in the “Register user” Local Integration Process we like to set a feedback message for the Telegram user. (As we did before for the unregister process.) So add a Content Modifier (7) and set its body tab (in Expression mode) with the following content:

{
	"chat_id": ${property.msgInChatId}, 
	"text": "Thanks for subscribing to the SAP CPI Telegram Bot! To stop your subscription, write: /stop"
}

You might wonder how these feedback messages will be transferred to the user, because since now we just set messages via Content Modifiers but didn’t send them anywhere. You’re right – there’s one more process needed. (If you have a look at the overview picture, you will find the next Local Integration Process in the upper right corner.) Let’s build the third Local Integration Process of todays IFlow.

Add another Local Integration Process (8) and name it “Send message to user”. This will act as a reusable pattern which allows us to send messages back to the Telegram servers. Start the new Local Integration Process by placing a Content Modifier (9) and set it up as shown below.

The Content-Type header has to be set to send the messages we configured in (3) and (7) with the appropriate type header to the Telegram serves. The botToken property should contain the bot token you received from the botfather, when setting up your bot. (Hint: You should externalize this parameter so that you can switch with ease between different bots/enviroments later .)

Add a Request Reply element now, name it something like “sendMessage()” and connect it (10) with a receiver element. Choose “HTTP” as channel type and configure the channel as shown below.

Optionally you can add a logging script (11), like the one you placed as very first element to your IFlow. That’s not necessary, but it can be helpful, if you want to understand the response messages from Telegram.

Now that we have setup all Local Integration Processes, we can connect/call them from the IFlow’s main logic. Hold out, most of it is done…

Let’s head back to our main Integration Process and place a Router element (12) to the process. Then add two Process Call elements (13), (14) and an end message event (15). Call the “User register” Local Integration Process in the first and the “User unregister” Local Integration Process in the second Process Call element. Connect the router with those three elements and configure the routes as shown below.

In case the message text is “/start” we will route the first Process Call element. In case it is “/stop” we route to the second Process Call element and in case it is everything else we do nothing (route to Message End event) because we don’t want our bot to answer other commands.

As last step, connect both Process Call elements with a new Process Call (16) element (which should call the sendMessage()-Local Integration Process) and connect this last Process Call with the Message End event. That’s it! Now our bot should be able to handle /start and /stop messages by saving the user’s id into SAP CPI’s datastore respectively by deleting it from the store.

IFlow Pt. 3 – Setting up the broadcast logic

In this third and last part of the IFlow setup we will build the broadcast logic (use-case 3), which will enable us to send messages to all registered bot users. The things we will setup look like this:

Start by adding a new Integration Process to your IFlow. I named mine “Integration Process / Broadcast message”. Then connect a (new) receiver with the Start element (17) and choose “HTTPS” as channel type.

Choose an endpoint address of your favour and remove the checkmark in the CSRF Protected box. (In case you want to build an productive interface out of this example, you should use CSRF protection. We only dectivate it for sake of easiness because CSRF isn’t the topic of this blog post.)

Next add a Content Modifier (18). We will use it to store message’s content (which contains the message which should be broadcasted to the bot users) because otherwise it will be lost after the next step.

Add a Data Store Operation/Select (19) as next element to your flow. We will use it to read the Telegram user ids of all users that registered to our bot before. Ensure that you use the same datastore name as in the register/unregister user processes and also make sure that you remove the checkmark from the “Delete on completen” checkbox. (Otherwise user will be dropped from datastore after they got their first broadcast message.)

The result of the Select element will be a huge XML-document containing all user ids. As I told you in the introduction of this article, there’s no API call on Telegrams API that allows us to send messages to multiple users in one API call. So we have to split our userbase and handle each user separately. Therefore we add an Iterating Splitter (20) as next element to our Integration Process.

The splitter will split the datastore result based on the “message” tag in XPath mode. The outcome of the splitter will be one message per user that flows independently through all following steps of our Integration Process.

After the splitter we have to prepare each message for its tranfer to the Telegram servers. Thus we are adding another Content Modifier (21) and read the user’s chat id from the message body via XPath to a property. This chat id then will be used in the “Message Body” tab of the Content Modifier for definition of the recipient.

Now that we prepared the message, we just have to send it to the Telegram user. Therefore we can re-use the Local Integration Process “sendMessage()” which we build a chapter ago. So add a (Local) Process Call element (22) and configure it to call the “sendMessage” Local Integration Process. As last step, connect the Process Call element with the End Message event. Gratulations – you made it!

Testing – let’s check the broadcast functionality

Let’s test our broadcast interface now. If not done, deploy the IFlow. Then find at least two users/colleagues who send a message with the text “/start” to your Telegram bot. If you made everything correct, the users should have received a response from your bot and you should see their Telegram ids in SAP CPIs datastore view.

Open a webservice testing tool like Postman and setup a new POST request. Set the target url to your IFlows endpoint and set a text to be broadcasted as body. Send the call and check if the users received your message.

If everything went fine, it should look as in the screenshot above.

Ideas and areas for improvement

We’re done with the main part of this article. In general you should be able to setup Telegram-based interfaces now. Before we end with a conclusion let’s have a look on the restrictions/downsides of the solution we just built. (Yes, there’s space for improvement…)

  • The user id storage is based on SAP CPI’s datastore feature. Since messages in the datastore have a maximum lifespan, for productive use you should do one of the following things:
    • Add some additional functionality, which reads all datastore entries, deletes them and rewrites them every X days. (Thus the entries never get deleted from the datastore.)
    • Use a different storage solution for the user ids like a JDBC database.
  • The Datastore/Select element needs a number of maximum ids to select. In our tutorial we set this value to 1000. This also means if you had more than 1000 users registered this would fail. Either implement something like a loop to pull out all/more ids or switch to another storage solution.
  • The Telegram API has some kind of API throttling. This means if you send too much messages within a short period (30 messages/second), your interface will be blocked. If you plan to handle a huge user base, you should implement somekind of throttle to ensure that your interface doesn’t send more than 30 messages per second.
  • What we skipped totally for now is some kind of authorization handling. If you plan to build a bot for sales/marketing reasons, this should be no problem. But if you plan to build a bot for company news or alerting purposes, you should implement some kind of user authorization in the “User register” process. (I will handle the topic in one of the upcoming blogs.)
  • If you want to learn more about Telegrams API capabilities, have a look at their API documenation: https://core.telegram.org/bots/api

Conclusion

Poof! the article has become longer than expected. I hope I could bring you closer to the topic “Telegram API” in connection with SAP CPI. If something is unclear, write me a comment. Feedback is always welcome.

If you still need a few ideas for use cases – how about …

  • A Telegram bot which sends SAP CPI Alerts (in combination with the CPI Message Processing Logs-API)
  • A Telegram bot to inform business users about upcoming maintenance periods
  • A Telegram bot for sending your companies monthly news letter

And now I wish you a lot of fun building it!

6 Comments
You must be Logged on to comment or reply to a post.
  • Hi Raffael Herrmann,

    I complated this senario but when I test it after more than three successfull trying, my suser is locking.Did you face this problem too?

     

    Thansks

    • Hi Metin, no, i didn’t run into this scenario. Are you sure it works three times and then locks your user? Because it sounds more like entering three times the wrong password and then your user gets locked. Maybe you have entered the password in a wrong format in the webhook url? If your password contains special chars, ensure that you encode them.

      Regards

  • Hi Raffael Herrmann,

    I configured everything as mentioned by you.

    But when I call the broadcast url I am getting error

    com.sap.it.rt.adapter.http.api.exception.HttpResponseException: An internal server error occured: HTTP operation failed invoking https://api.telegram.org/bot${property.botToken}/sendMessage  (where ${propert.botToken} is my bot token)

    But if I send request directly to https://api.telegram.org/bot${property.botToken}/sendMessage I receive the message in telegram.

     

     

    Regards

    • You are getting an HTTP400 error in CPI from Telegram. 400 means “Bad Request” and often is thrown when the payload send to the service doesn’t match the service’s requirements. From you screenshot I can see that your JSON, sent out to Telegram, contains the field “chat_Id” with an uppercase I. But I think it should be “chat_id” with an lowercase. Could you please change “chat_Id” to “chat_id” and try if this solves your problem?

  • Hi, I am quite new to here and I am having trouble to find the IFlow. Is that a website or an application?

     

    Looking forward from your reply,

    Seng