Skip to main content

Telegram Integration for AlmaForge

AlmaForge can post access-request notifications to a Telegram chat, group, or channel. For each request, the bot sends a message with the requester, the requested role or resource, the reason, and a link back to AlmaForge. When the request is approved, denied, or expires, the same message is edited in place to keep the chat up to date.

The integration is built into almad. You do not install a separate plugin, run a callback service, or expose a public URL to Telegram. Configure it with a TelegramConnector resource, either under Integrations in the Web UI or with alma apply. Both paths store the same resource.

TL;DR​

  1. Complete Step 1 and copy the bot token.
  2. Add the bot to your destination chat (Step 2).
  3. Grab the chat ID (Step 3).
  4. Apply the example TelegramConnector from Step 4.
  5. File a test access request to confirm the message lands in the chat (Step 5).

Limitations​

The integration is currently one-way. It sends notifications to Telegram, but it does not accept approvals or denials from Telegram. Reviews still happen in AlmaForge, either in the Web UI or with alma request review. This is intentional: AlmaForge has no trustworthy way to bind a Telegram chat identity to the SSO identity that should review the request. Telegram display names and usernames are self-asserted, and the Bot API does not return a verifiable identity claim that AlmaForge could safely match against its own user directory. In practice, an "Approve" button in the chat would let any chat member act as a reviewer, so reviews stay inside AlmaForge, where the reviewer is already authenticated with normal SSO credentials and the decision is recorded under their real identity.

Prerequisites​

Before you start, make sure you have:

  • A running AlmaForge cluster with an Auth Service that you can reach over the network and a working alma CLI pointed at it.
  • A requester allowed to request access and a different user allowed to review that request. See Just-in-Time Access.
  • A Telegram account that can talk to @BotFather, Telegram's official bot creation bot.
  • Outbound HTTPS egress from almad to https://api.telegram.org from every auth host. The integration makes no inbound connections, so no firewall or NAT rules on the AlmaForge side are required.
  • A Telegram chat, group, or channel where the bot will post, and permission to add a bot to it.

Step 1. Create a bot with BotFather​

Open Telegram. Search for the user @BotFather (it has a verified blue check) and start a chat with it.

Send /newbot. BotFather will ask you two things, in order:

  1. A display name for the bot. Pick something that makes its purpose obvious in your chats, for example AlmaForge Access Requests.
  2. A username for the bot. It must end in bot (for example almaforge_access_bot) and must be unique across all of Telegram.

BotFather replies with a message that contains the bot token, a string of the form 123456789:ABCdefGHIjklMNOpqrSTUvwxYZ0123456789. Store it in your secret manager. BotFather can re-send it via /token. Access to stored connector secrets requires permission to read secrets.

Treat the bot token as a password

Anyone with the bot token can post messages as your bot, edit existing messages, and read the chats the bot is in. Store it in the same secret manager you use for production credentials. Do not commit it to Git.

Privacy mode

By default BotFather creates bots with privacy mode enabled, which means the bot only sees messages that directly mention it or reply to it. Leave this setting alone. The integration is one-way and never reads incoming messages at all, so privacy mode has no effect on it.

Step 2. Add the bot to its chats​

For each destination you plan to list in roleToRecipients:

  • Group or supergroup: open the group's info panel, tap Add Members, search for the bot's username, and add it. The bot joins silently.
  • Channel: open the channel's info panel, tap Administrators, add the bot as an admin, and grant it the Post Messages permission. A regular member bot cannot post to a channel.
  • Private chat with a human: the bot cannot start a direct conversation. You must open a chat with the bot first and press Start (or send any message). This is a Telegram platform rule, not a AlmaForge limitation.

This invite step is the most common source of "why isn't the integration posting?" tickets. A bot that has not been added to a chat cannot post to it, and Telegram returns Forbidden: bot is not a member of the group chat on the first post attempt.

Step 3. Obtain the chat ID​

Telegram's Bot API routes messages by a numeric chat_id, not by the human-friendly name you see in the app. You need the ID once, at setup time, and paste it into the connector.

Method A: Telegram Web URL bar​

  1. Open web.telegram.org in a browser and sign in with the same account that owns the chat.
  2. Navigate to the destination chat, group, or channel.
  3. Look at the URL in the browser address bar. It has the shape https://web.telegram.org/a/#-1001234567890.
  4. The number after the # is the chat ID. Copy it exactly, including the leading - if present.

Groups and channels have negative IDs. Private chats with individual users have positive IDs.

Method B: curl from a shell​

If you run headless or do not use Telegram Web, you can ask Telegram for the chat ID directly via the Bot API method getUpdates:

  1. Send a command addressed to the bot in the destination group, such as /start@almaforge_access_bot. In a private chat, send /start. This produces an update the bot can receive with privacy mode enabled.

  2. From a machine with outbound HTTPS, run:

    Terminal
    curl "https://api.telegram.org/bot<TOKEN>/getUpdates"

    Replace <TOKEN> with the bot token from Step 1.

  3. The response is JSON. Find the chat.id field inside any update that came from your destination chat. That is the chat ID.

Updates expire after 24 hours

Telegram keeps pending updates for the bot for 24 hours, then drops them. If getUpdates returns an empty result array, either no one has messaged the chat recently or the queue has already been drained by a previous call. Post a fresh message and try again.

Once you have the chat ID, you never need to repeat this step for that chat. Save it with your connector configuration.

Step 4. Apply a TelegramConnector​

Write a YAML file describing the connector:

telegram.yaml:

examples/integrations/telegram.yaml
apiVersion: almaforge.com/v1
kind: TelegramConnector
metadata:
name: telegram
spec:
# Replace every value below before applying.
botToken: replace-me
roleToRecipients:
# '*' is the default recipient and is required.
'*':
values: ['-1001234567890']

For a quick setup, keep botToken inline as shown above. For production, keep the secret out of the manifest and load it from a file or environment variable instead. See Secret management in this page's Operations section.

Replace:

  • The bot token with the value from Step 1.
  • The chat IDs in roleToRecipients with the values from Step 3.

roleToRecipients routes requests for specific roles to specific chats. The key is the AlmaForge role name a user requests. The values are Telegram chat identifiers, either a numeric chat_id or a public @channelusername. The "*" key is the required default and matches any role that has no explicit entry. A user who requests several roles receives the union of the matched recipient lists, deduplicated.

Use chat IDs, not @usernames where possible

A numeric chat_id (e.g. -1001234567890) is the canonical and stable identifier. @channelusername works for public channels and supergroups that have a username set, but if the username is later changed in Telegram, the connector stops resolving. Use numeric chat IDs unless you specifically need the convenience of a public channel username.

Apply the connector:

Terminal
alma apply -f telegram.yaml
One connector per cluster

Currently, only one Telegram integration is supported per cluster. The connector must be named telegram. Any TelegramConnector applied under a different name is ignored.

Step 5. Test end-to-end​

The plugin polls the TelegramConnector resource once a minute and reconciles its state against what it observes. It starts on first appearance, restarts on resource-version change, and stops on delete. Apply, edit, and delete are all picked up within about 60 seconds with no daemon restart.

Tail the almad logs on your auth host and look for the plugin's start lines:

Terminal
journalctl -u almad -f | grep telegram_service...  telegram_service: connector found; starting plugin  recipient_roles=2...  Plugin is ready

Now create a real access request and confirm a Telegram message appears in the target chat:

Terminal
alma request create --roles access --nowait --reason "telegram smoke test"

As a different user with permission to review this request, approve or deny it in AlmaForge. Use the request ID printed by the create command:

Terminal
alma request review <request-id> --approve --reason "smoke test ok"

The matching Telegram message updates in place to show the resolution, the reviewer's name as it appears in AlmaForge, and the resolution reason. There is no second message: the original message is edited via Telegram's editMessageText API.

Troubleshooting​

Bot token is wrong or revoked​

Unauthorized (HTTP 401). The bot token is wrong, has been revoked via BotFather /revoke, or has a stray whitespace character. Re-copy it from BotFather and re-apply the connector. If the token comes from a file, check that the file does not contain a trailing newline from your editor.

Bot is not in the destination chat​

Forbidden: bot is not a member of the group chat. The bot has not been added to one of the chats listed in roleToRecipients, or it was kicked. Add the bot in the Telegram client and try again. No polling tick is needed on the Telegram side.

Chat ID is invalid​

Bad Request: chat not found. The chat ID in roleToRecipients is wrong. The most common mistakes are:

  • Dropping the leading - from a group or supergroup ID.
  • Pasting the ID of a different chat by accident.
  • Using a @channelusername whose channel was renamed.

Re-derive the chat ID via either method in Step 3 and re-apply the connector.

Plugin starts and immediately exits​

The most common causes are an unreachable api.telegram.org (for example, an egress problem from the auth host), a wrong spec.apiURL override, or a token-file URI pointing at a path the almad user cannot read. Tail the auth journal for the plugin component to see the underlying error. The plugin retries on the next poll tick (about 60 seconds), so a transient network problem self-recovers.

Operations​

Lifecycle​

The Telegram plugin lives inside the almad binary and is enabled by default on every process that runs the Auth Service. To turn it on or off for a specific host, pass --telegram-service or --no-telegram-service on the almad start command line, or set ALMA_TELEGRAM_SERVICE to true or false. Both forms work under systemd drop-ins, container environments, and Kubernetes pod specs.

Currently, only one Telegram integration is supported per cluster. Use telegram as the connector's metadata.name. That is the name the runtime watches for. The connector is reloaded every 60 seconds, so applying it starts the plugin, editing it triggers a restart on the next reload, and deleting it stops the plugin. None of these transitions require a almad restart.

High availability​

The plugin runs on every auth process, including in multi-node clusters. New access requests produce exactly one Telegram message regardless of how many auth processes are running, and resolution edits work correctly even if the auth host that posted the original message has since restarted.

Egress requirements​

Auth hosts require outbound TCP/443 to api.telegram.org. No inbound connectivity from Telegram is required, and the integration does not open any listening sockets.

Sidecar deployment​

The plugin can also run as a dedicated sidecar process: a standalone almad instance that joins the existing cluster solely to bridge Telegram. This topology is appropriate when the auth nodes themselves cannot reach api.telegram.org and a separate host is used as the egress point.

On the auth cluster, mint a join token that grants the Telegram system role:

Terminal
alma create token --services=telegram --expires=1h

On the sidecar host, start almad with the token and the Telegram role enabled. No configuration file is required:

Terminal
export ALMA_CLIENT_SECRET=/path/to/client-secretalmad start \    --auth-server=auth.example.com:443 \    --client-id=<client-id> \    --telegram-service

The same parameters can be supplied as environment variables for container or systemd deployments:

Terminal
export ALMA_AUTH_SERVER=auth.example.com:443export ALMA_CLIENT_ID=<client-id>export ALMA_CLIENT_SECRET=/path/to/client-secretexport ALMA_TELEGRAM_SERVICE=truealmad start

The sidecar joins the cluster as system:telegram, receives an identity certificate from the auth service, polls the TelegramConnector/telegram resource over the standard auth gRPC channel, and runs the plugin locally.

Secret management​

For production, keep the bot token outside the manifest. spec.botToken can load it from a file or environment variable. See Resource value expansion for the exact syntax and field support. Rotate the token by updating the underlying secret and re-applying the connector. No almad restart is required.

Identity and permissions​

The Telegram integration runs under a built-in system role called Telegram. almad grants it the minimum set of permissions it needs and nothing more: list and read on ClusterName, list and readwithsecrets on TelegramConnector so it can load its bot token, read on CertificateAuthority, and list, read and update on AccessRequest and AccessPluginData. No hand-authored RBAC resources are required, and the role cannot be used to sign in as a human.

Every Telegram message is posted by the bot user you created with BotFather. In the chat, every action taken by the integration appears as that bot, so name the bot something that makes the source obvious.

Monitoring​

The auth service logs plugin lifecycle events (start, stop, restart on connector change) and Telegram Bot API errors under the telegram component. Tail the journal with:

Terminal
journalctl -u almad -f | grep -i telegram

A successful start prints connector found; starting plugin followed by Plugin is ready. Connector changes log connector changed; restarting plugin. Bot API errors are logged with the HTTP status code and the description Telegram returned.

The integration does not emit Telegram-specific audit events. Every AlmaForge access request and review already produces a structured audit event in the auth service journal, and the plugin lifecycle lines above are usually enough to confirm what the bot did in response. Every Telegram message includes the request ID, so it is straightforward to correlate a chat notification with a specific access request.

Removal​

Delete the connector resource:

Terminal
alma delete TelegramConnector/telegram

The plugin stops on the next polling tick, within about 60 seconds, with no almad restart. Existing Telegram messages are left in place. The integration never deletes messages on its own.

To remove the bot from its chats, do so through the normal Telegram client. To revoke the bot token entirely, send /revoke to @BotFather.

References​