Playbook: Telegram Bot Token Rotation
Service: Telegram BotFather (https://t.me/BotFather)
Secrets: TELEGRAM_BOT_TOKEN, TELEGRAM_P24_CLAUDE_BOT_API_KEY
SOPS file: secrets/n8n-bms4.env.sops
Rotation frequency: 90 days (or immediately on suspected exposure)
Last rotated: 2026-06-27
Next due: 2026-09-27
Token inventory
| Key name | Bot purpose | Format |
|---|---|---|
TELEGRAM_BOT_TOKEN | Main n8n Telegram bot — receives commands, sends notifications | <bot_id>:<token> (e.g. 123456789:AAF...) |
TELEGRAM_P24_CLAUDE_BOT_API_KEY | p24 Claude bot — Claude-specific commands and callbacks | <bot_id>:<token> |
Both tokens follow the same format:
<numeric_bot_id>:<43-char-alphanumeric-token>. The<bot_id>part does not change on rotation — only the token after the colon is replaced. Chat IDs (TELEGRAM_CHAT_ID,WAHA_TELEGRAM_CHAT_ID) are unaffected by token rotation.
What uses these credentials
| Consumer | Key | Effect if missing |
|---|---|---|
| n8n Telegram nodes on bms-4 | TELEGRAM_BOT_TOKEN | Telegram message sending/receiving fails |
| p24 Claude callback workflows | TELEGRAM_P24_CLAUDE_BOT_API_KEY | Claude bot responses stop |
| n8n credential “Telegram” | Both tokens | All Telegram-triggered n8n workflows fail |
Where stored
| Location | Key name | How to update |
|---|---|---|
secrets/n8n-bms4.env.sops | TELEGRAM_BOT_TOKEN, TELEGRAM_P24_CLAUDE_BOT_API_KEY | SOPS write pattern |
| bms-4 deployed env | /opt/p24-infra/bms-4/.env | Auto-synced by secrets-sync.yml on merge |
| n8n credentials on bms-4 | ”Telegram” credential(s) | Update via n8n UI after SOPS change |
Login credentials
BotFather is a Telegram bot itself — no web portal. Access options:
- Telegram web:
https://web.telegram.org(Playwright-accessible) - Telegram desktop app (installed locally)
- Phone: The Telegram account linked to radieu@gmail.com or the registered phone number
Automation status
Playwright-automatable via Telegram Web — BotFather commands can be sent via https://web.telegram.org if the Telegram session is active in the browser profile. If the session is expired, re-login requires a phone verification code (OTP to phone — Tier 3 blocker).
Check if Telegram web session is active before spawning Playwright:
- Navigate to https://web.telegram.org — if it loads the chat list without requesting a phone number, the session is valid.
Rotation steps
Option A — Playwright agent via Telegram Web (if session active)
You are rotating Telegram bot tokens: TELEGRAM_BOT_TOKEN and TELEGRAM_P24_CLAUDE_BOT_API_KEY.
IMPORTANT: Never display any secret value in your response. Reference key names only.
1. Use Playwright to navigate to https://web.telegram.org
- If session is active (chat list loads), proceed
- If login is required (phone number requested), abort and use Option B (manual phone OTP needed)
2. Search for BotFather in the search bar → open the BotFather chat
3. For TELEGRAM_BOT_TOKEN:
→ Send: /revoke
→ BotFather lists your bots → select the main n8n bot
→ BotFather confirms revocation and shows new token
→ Copy new token into $env:NEW_BOT_TOKEN (format: "123456789:AAF...", never print)
4. For TELEGRAM_P24_CLAUDE_BOT_API_KEY:
→ Send: /revoke
→ Select the p24 Claude bot
→ Copy new token into $env:NEW_CLAUDE_BOT_TOKEN (never print)
5. Close the browser
6. Update secrets/n8n-bms4.env.sops:
$env:SOPS_AGE_KEY_FILE = "C:\Users\konar\.age\p24-infra-keys.txt"
$plain = sops --decrypt --input-type dotenv --output-type dotenv secrets\n8n-bms4.env.sops
$temp = "$PWD\secrets\n8n-bms4-edit.env.sops"
$updated = $plain -replace "^TELEGRAM_BOT_TOKEN=.*", "TELEGRAM_BOT_TOKEN=$env:NEW_BOT_TOKEN"
$updated = $updated -replace "^TELEGRAM_P24_CLAUDE_BOT_API_KEY=.*", "TELEGRAM_P24_CLAUDE_BOT_API_KEY=$env:NEW_CLAUDE_BOT_TOKEN"
[System.IO.File]::WriteAllText($temp, ($updated -join "`n") + "`n", [System.Text.UTF8Encoding]::new($false))
$enc = sops --encrypt --input-type dotenv --output-type dotenv $temp
[System.IO.File]::WriteAllText("$PWD\secrets\n8n-bms4.env.sops", ($enc -join "`n") + "`n", [System.Text.UTF8Encoding]::new($false))
sops --decrypt --input-type dotenv --output-type dotenv secrets\n8n-bms4.env.sops | Out-Null
if ($LASTEXITCODE -ne 0) { throw "SOPS corrupt — do NOT commit" }
[System.IO.File]::Delete($temp)
$env:NEW_BOT_TOKEN = ""; $env:NEW_CLAUDE_BOT_TOKEN = ""
7. Update n8n Telegram credentials on bms-4:
Navigate to https://n8n.bms-4.infra.zintegrowana.online/credentials
→ Find Telegram credential(s) → Edit → update Access Token field → Save
8. Commit and PR:
git checkout -b fix/rotate-telegram-bot-tokens origin/main
git add secrets/n8n-bms4.env.sops
git commit -m "chore: rotate TELEGRAM_BOT_TOKEN + TELEGRAM_P24_CLAUDE_BOT_API_KEY (scheduled 90d)"
git push -u origin fix/rotate-telegram-bot-tokens
gh pr create --base main --title "chore: rotate Telegram bot tokens"
gh pr merge --merge --delete-branch
9. Append to docs/secrets-rotation-log.md:
| <YYYY-MM-DD HH:MM UTC> | — | TELEGRAM_BOT_TOKEN + TELEGRAM_P24_CLAUDE_BOT_API_KEY | scheduled 90d rotation | AI-agent | SOPS (n8n-bms4) + n8n credentials |
Option B — Manual via Telegram app or BotFather
Step 1 — Open BotFather
Option 1: Telegram desktop app → search BotFather → open chat
Option 2: Phone → Telegram → search BotFather
Option 3: https://web.telegram.org → BotFather (requires active session)
Step 2 — Revoke and get new tokens
For each bot (main n8n bot, p24 Claude bot):
→ Send: /revoke
→ Select the bot by name
→ Copy the new token shown (format: <bot_id>:<token>)
Step 3 — Store in .env.local then hand off to Claude:
Add to d:\code_2026\p24-infra\.env.local:
TELEGRAM_BOT_TOKEN_NEW=<new-token>
TELEGRAM_P24_CLAUDE_BOT_API_KEY_NEW=<new-token>
Tell Claude "Telegram tokens updated in .env.local" — Claude handles SOPS + PR.
Step 4 — Update n8n Telegram credential on bms-4:
URL: https://n8n.bms-4.infra.zintegrowana.online/credentials
→ Find Telegram credential → Edit → update Access Token → Save
Verification
$env:SOPS_AGE_KEY_FILE = "C:\Users\konar\.age\p24-infra-keys.txt"
$plain = sops --decrypt --input-type dotenv --output-type dotenv secrets\n8n-bms4.env.sops
$t1 = ($plain | Select-String "^TELEGRAM_BOT_TOKEN=").ToString().Split('=',2)[1]
$t2 = ($plain | Select-String "^TELEGRAM_P24_CLAUDE_BOT_API_KEY=").ToString().Split('=',2)[1]
Write-Host "TELEGRAM_BOT_TOKEN length: $($t1.Length)" # expected: ~52 (numeric_id:43chars)
Write-Host "TELEGRAM_P24_CLAUDE_BOT_API_KEY length: $($t2.Length)" # expected: ~52
# Quick API check (getMe — no cost, confirms token validity)
$env:T = $t1
$r = Invoke-RestMethod "https://api.telegram.org/bot$env:T/getMe"
Write-Host "Telegram bot: $($r.result.username)"
$env:T = ""; $env:SOPS_AGE_KEY_FILE = ""Escalation
| Symptom | Action |
|---|---|
| Telegram web requires phone OTP | Use Telegram desktop app or phone to run /revoke manually (Option B) |
BotFather /revoke shows no bots listed | Ensure you are logged in as the correct Telegram account that owns the bots |
| Bot stops responding immediately after revoke | Expected — old token is instantly invalidated; n8n must be restarted to pick up new token from env |
| n8n still using old token after secrets-sync | Restart n8n workers: ssh root@54.36.123.110 "cd /opt/p24-infra/bms-4 && docker compose restart n8n n8n-worker-1 n8n-worker-2 n8n-worker-3" |
Prevention
- Telegram bot tokens do not expire but are immediately revocable.
- The
credential-rotation.ymlGH Actions workflow (Monday 06:00 UTC) checksnext_dueand opens ahuman-actionissue. - Keep Telegram web session active in the browser profile to enable Playwright automation.
- After rotation, update Last rotated and Next due at the top of this playbook.
Audit Log — Log to infra_operations
After this operation completes, log it to the infra_operations audit table.
Python (Linux server — bms-4, vps-i1, vps-h1, or similar):
import sys
sys.path.insert(0, '/opt/p24-infra')
from scripts.lib.log_op import log_op
log_op(
actor="claude", # "radieu" for manual human ops, "claude" for agent
op_type="credential_rotation",
resource="TELEGRAM_BOT_TOKEN",
result="success", # "success" | "failed" | "skipped"
detail="Scheduled rotation — Telegram bot token regenerated via BotFather and SOPS updated",
env="bms-4",
gh_issue=2730,
)PowerShell (Windows dev machine):
$env:SUPABASE_URL = (Get-Content "C:\code_2026\p24-infra\.env.local" | Select-String "^SUPABASE_URL=").ToString().Split("=",2)[1].Trim()
$env:SUPABASE_SERVICE_KEY = (Get-Content "C:\code_2026\p24-infra\.env.local" | Select-String "^SUPABASE_SERVICE_KEY=").ToString().Split("=",2)[1].Trim()
python -c "
import os, sys
sys.path.insert(0, 'C:/code_2026/p24-infra')
from scripts.lib.log_op import log_op
log_op('claude', 'credential_rotation', 'TELEGRAM_BOT_TOKEN', 'success', 'Scheduled rotation — Telegram bot token regenerated via BotFather and SOPS updated', 'bms-4')
"
$env:SUPABASE_URL = ''; $env:SUPABASE_SERVICE_KEY = ''