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 nameBot purposeFormat
TELEGRAM_BOT_TOKENMain n8n Telegram bot — receives commands, sends notifications<bot_id>:<token> (e.g. 123456789:AAF...)
TELEGRAM_P24_CLAUDE_BOT_API_KEYp24 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

ConsumerKeyEffect if missing
n8n Telegram nodes on bms-4TELEGRAM_BOT_TOKENTelegram message sending/receiving fails
p24 Claude callback workflowsTELEGRAM_P24_CLAUDE_BOT_API_KEYClaude bot responses stop
n8n credential “Telegram”Both tokensAll Telegram-triggered n8n workflows fail

Where stored

LocationKey nameHow to update
secrets/n8n-bms4.env.sopsTELEGRAM_BOT_TOKEN, TELEGRAM_P24_CLAUDE_BOT_API_KEYSOPS write pattern
bms-4 deployed env/opt/p24-infra/bms-4/.envAuto-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

SymptomAction
Telegram web requires phone OTPUse Telegram desktop app or phone to run /revoke manually (Option B)
BotFather /revoke shows no bots listedEnsure you are logged in as the correct Telegram account that owns the bots
Bot stops responding immediately after revokeExpected — old token is instantly invalidated; n8n must be restarted to pick up new token from env
n8n still using old token after secrets-syncRestart 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.yml GH Actions workflow (Monday 06:00 UTC) checks next_due and opens a human-action issue.
  • 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 = ''