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
- Complete Step 1 and copy the bot token.
- Add the bot to your destination chat (Step 2).
- Grab the chat ID (Step 3).
- Apply the example
TelegramConnectorfrom Step 4. - 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
almaCLI pointed at it. - A requester allowed to request
accessand 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
almadtohttps://api.telegram.orgfrom 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:
- A display name for the bot. Pick something that makes its
purpose obvious in your chats, for example
AlmaForge Access Requests. - A username for the bot. It must end in
bot(for examplealmaforge_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.
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.
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
- Open web.telegram.org in a browser and sign in with the same account that owns the chat.
- Navigate to the destination chat, group, or channel.
- Look at the URL in the browser address bar. It has the shape
https://web.telegram.org/a/#-1001234567890. - 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:
-
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. -
From a machine with outbound HTTPS, run:
curl "https://api.telegram.org/bot<TOKEN>/getUpdates"Replace
<TOKEN>with the bot token from Step 1. -
The response is JSON. Find the
chat.idfield inside any update that came from your destination chat. That is the chat ID.
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:
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
roleToRecipientswith 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.
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:
alma apply -f telegram.yamlCurrently, 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:
journalctl -u almad -f | grep telegram_service... telegram_service: connector found; starting plugin recipient_roles=2... Plugin is readyNow create a real access request and confirm a Telegram message appears in the target chat:
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:
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
@channelusernamewhose 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:
alma create token --services=telegram --expires=1hOn the sidecar host, start almad with the token and the
Telegram role enabled. No configuration file is required:
export ALMA_CLIENT_SECRET=/path/to/client-secretalmad start \ --auth-server=auth.example.com:443 \ --client-id=<client-id> \ --telegram-serviceThe same parameters can be supplied as environment variables for container or systemd deployments:
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 startThe 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:
journalctl -u almad -f | grep -i telegramA 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:
alma delete TelegramConnector/telegramThe 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.