Playbook: Discord Credential Rotation

Covers: DISCORD_BOT_TOKEN · discord_radieu_password · DISCORD_WEBHOOK_URL


Overview

KeySOPS file(s)Purpose
DISCORD_BOT_TOKENmonitoringDiscord bot for infra alerts; token reset requires 2FA
discord_radieu_passwordmonitoringAccount password for the radieu Discord user
DISCORD_WEBHOOK_URLn8n-bms4, vps-h1Incoming webhook for script error notifications

DISCORD_BOT_TOKEN and discord_radieu_password are Tier 3 Manual — no rotation API exists for either (bot token reset and account password change are both Developer Portal / account- settings UI operations gated by 2FA).

DISCORD_WEBHOOK_URL is Tier 1 Fully Autonomous, not Tier 3 — corrected twice on 2026-08-04 (#2889/#2666). First correction claimed Tier 2 via Playwright; that was superseded the same day after a live test found Playwright’s login hits an unsolvable hCaptcha challenge, while the REST API path (DISCORD_BOT_TOKEN + the bot holding Manage Webhooks on the channel) worked end-to-end with zero login/2FA/CAPTCHA involved — see docs/playbooks/discord-provisioning.md §Step 2b. No TOTP prerequisite, no human-supervised session, no browser at all — genuinely Tier 1 once the bot has been granted Manage Webhooks on a given channel (a one-time per-channel human grant, not part of the rotation itself). The Playwright path in that same playbook remains documented as a fallback but is currently non-functional (hCaptcha). The steps below are kept as the manual UI fallback for when neither automated path is available, and for the two genuinely Tier 3 keys in this file.

When to rotate

  • DISCORD_BOT_TOKEN: On exposure immediately. Scheduled: annually.
  • discord_radieu_password: On exposure; scheduled: annually.
  • DISCORD_WEBHOOK_URL: Only if compromised or channel deleted. Webhook URLs do not expire.

Pre-rotation checklist

  • 2FA app accessible (bot token reset requires Discord 2FA)
  • No active bot operations in flight (bot reconnects automatically after token reset)

Rotation steps

DISCORD_BOT_TOKEN

1. Get new token (human required)

  1. https://discord.com/developers/applications → select the p24-infra bot
  2. Bot tab → Reset Token → confirm 2FA prompt
  3. Copy the new token — shown once only

2. Distribute (Claude handles)

Update secrets/monitoring.env.sops — follow docs/playbooks/sops-edit-operations.md:

$env:SOPS_AGE_KEY_FILE = "C:\Users\konar\.age\p24-infra-keys.txt"
# Edit DISCORD_BOT_TOKEN in monitoring.env.sops per sops-edit-operations.md
# Canary after write:
sops --decrypt --input-type dotenv --output-type dotenv secrets\monitoring.env.sops | Out-Null
if ($LASTEXITCODE -ne 0) { throw "SOPS corrupt — do NOT commit" }

No container restart needed — the Discord bot reconnects on the next event cycle.

3. Verify

The bot will show as Online in its Discord server within 60 seconds.


discord_radieu_password

1. Change password (human required)

Discord app → User Settings → My Account → Change Password → set new strong password.

2. Distribute (Claude handles)

Update secrets/monitoring.env.sops — key: discord_radieu_password. No service restarts required.


DISCORD_WEBHOOK_URL

Rotate only if the webhook is compromised or its channel is deleted.

1. Create new webhook — REST API (preferred, fully autonomous, no login)

Use docs/playbooks/discord-provisioning.md §Step 2b:

python3 discord-provisioning.py --action create-webhook \
  --webhook-name "<name>" --guild <GUILD_ID> --channel <CHANNEL_ID> \
  --sops-file secrets/n8n-bms4.env.sops --sops-file2 secrets/vps-h1.env.sops \
  --secret-key DISCORD_WEBHOOK_URL --issue <ISSUE_NUM>

This tries the REST API automatically (no flag needed) and writes both SOPS files + the rotation log in one step — skip §2 below when it succeeds. Prerequisite: DISCORD_BOT_TOKEN valid in secrets/monitoring.env.sops and the bot holding Manage Webhooks on the target channel (a one-time per-channel human grant — Server Settings → Roles → the bot’s role → Manage Webhooks). No login, no TOTP, no human-supervised session needed for this path.

Fallback if REST 403s (bot lacks the permission on this channel) — Playwright: the same CLI command falls back automatically, but as of 2026-08-04 this path is known blocked by an unsolvable hCaptcha challenge at login (see discord-provisioning.md status note) — expect it to fail, not just require supervision. Grant the bot Manage Webhooks on the channel instead of relying on this fallback.

Fallback — manual (human required): if neither automated path works: Discord server → channel Settings → Integrations → Webhooks → New Webhook → copy URL.

2. Distribute (manual-path only — the Playwright path in step 1 already does this)

Update secrets/n8n-bms4.env.sops and secrets/vps-h1.env.sops: key DISCORD_WEBHOOK_URL. Run canary on both files before git add.

Restart consumers that cache the URL at startup:

ssh root@100.86.202.85 "cd /opt/n8n && docker compose restart n8n"

3. Revoke old webhook

REST API (preferred): discord-provisioning.py --action delete-webhook --webhook-name "<old-name>" --guild <GUILD_ID> --channel <CHANNEL_ID> — only after the new URL is confirmed live everywhere. Falls back to Playwright automatically (currently hCaptcha-blocked, see §1 above) if REST fails. Manual fallback: Discord → channel Settings → Integrations → Webhooks → find old entry → Delete.


Log rotation

Append to docs/secrets-rotation-log.md:

| YYYY-MM-DD HH:MM UTC | #ISSUE | DISCORD_BOT_TOKEN | reason | radieu | monitoring.env.sops |

Recovery

  • Bot token reset portal error: wait 5 minutes, retry. Old token remains valid until explicitly reset.
  • Webhook alerts stopped after URL change: check n8n workflow logs on bms-4 for HTTP 404/410.

References

  • secret-rotation-access-matrix.md — Tier classification (Tier 1 for DISCORD_WEBHOOK_URL via REST API, Tier 3 for the other two keys in this file), cross-file duplication table
  • docs/playbooks/discord-provisioning.md — REST API (primary) + Playwright (hCaptcha-blocked fallback) for webhook create/delete
  • docs/playbooks/sops-edit-operations.md — SOPS write pattern