Skip to content

Discord Setup

Overview

Studio Jadu runs an official Discord community for Jadu Quest creators (proposal dated 2026-08-21). This guide collects every Discord integration we operate, where the code lives, and how to set each one up. For who posts what, when, the weekly POC rotation and how the team responds, see the Discord Operations doc (Google Docs).

Integration What it does Where it runs Code
Verified Creator bot /verify slash command + #start-here button → email OTP → assigns the Verified Creator role jadu-auth backend (App Runner) jadu-auth/be/src/discord/bot
Discord → Slack relay Polls selected Discord channels every 5 min and forwards new messages to Slack Cloudflare Worker (cron) discord-poll-to-slack Cloudflare dashboard
Content drafts pipeline Daily n8n job: fetch StudioJadu socials → Claude drafts Discord posts → Slack for human approval n8n (n8n-internal.scenarix.ai) ~/Documents/scenarix/n8n_flows/StudioJadu Discord Content Drafts.json
Feedback → Linear bot TODO, not set up yet. Buttons on a pinned #feedback forum post → modal → Linear issue + forum thread; Done/Canceled posted back Cloudflare Worker (planned) discord-worker/ in auto-pr, not yet pushed (see TODO)

One shared Discord application

All bots use the same Discord application from the Developer Portal. There is one application, not one per environment. The auth backend currently owns its Interactions Endpoint URL (for /verify). Resetting the bot token breaks every service using it, so coordinate before resetting.

Prerequisites (all integrations)

  • Developer Mode in Discord: User Settings → Advanced → Developer Mode. Needed to right-click and Copy ID on servers, channels, roles, and messages.
  • Admin access to the Studio Jadu Discord server and to the Discord application in the Developer Portal.
  • IDs you will need repeatedly:
    • Application ID and Public Key — Developer Portal → app → General Information.
    • Bot Token — Developer Portal → app → Bot tab.
    • Guild (server) ID — right-click the server icon → Copy Server ID.
    • Channel IDs — right-click a channel → Copy Channel ID.
    • Role IDs — Server Settings → Roles → right-click → Copy Role ID.

1. Verified Creator verification bot

Members link their Jadu account entirely inside Discord (no browser, no Discord OAuth) and are auto-assigned the Verified Creator role, which gates the JaduQuest channels.

Full details: the jadu-auth backend README → Discord "Verified Creator" role (also mirrored in this portal under jadu-auth) and the Discord block in be/.env.example.

Flow

/verify  or  #start-here "Verify your account" button
  → email modal
  → real Jadu OTP sent (same 60s resend cooldown as web login) → ephemeral reply + "Enter code" button
  → code modal
  → OTP checked (proves email ownership, no session created) → discordLinks record
  → RBAC auto-grant (discord_verify app) → bot assigns role via PUT /guilds/{g}/members/{u}/roles/{r}
  → ephemeral "✅ Verified!"

The Discord identity comes from the signed interaction payload (member.user), so there is nothing to spoof and no refresh token to store.

Why a bot, not Discord Linked Roles

Linked Roles requires the member to manually claim the role afterwards (Discord's own docs say so; no API workaround) and is buried in a server-name dropdown. A bot with Manage Roles assigns an ordinary role directly, which is how Patreon, Collab.Land, Ko-fi, etc. do it. The original 2026-08-26 plan was Linked Roles backed by sso.studiojadu.com; it was replaced by this bot flow.

Developer Portal setup

  1. Open the shared Studio Jadu app. Copy Application ID → DISCORD_CLIENT_ID, Public Key → DISCORD_PUBLIC_KEY.
  2. Bot tab → copy Token → DISCORD_BOT_TOKEN.
  3. OAuth2 → URL Generator → scope bot + permission Manage Roles. Open the URL and invite the bot to the server. (Enabling only slash commands or Linked Roles does not make the bot a guild member.)
  4. General Information → Interactions Endpoint URL → https://<auth-backend-host>/api/discord/interactions. The endpoint is only mounted when DISCORD_PUBLIC_KEY is set, and Discord will reject the save unless the live server answers its signed PING.

Server setup

  1. Create a plain (non-Linked) role, e.g. Verified Creator → DISCORD_ROLE_ID.
  2. Server Settings → Roles: drag the bot's own role above the Verified Creator role. Discord's hierarchy rule makes the grant call return 403 otherwise.
  3. Copy the server ID → DISCORD_GUILD_ID and the #start-here channel ID → DISCORD_START_HERE_CHANNEL_ID.

Environment variables (be/.env in the jadu-auth repo, and App Runner for prod)

Variable Purpose
DISCORD_CLIENT_ID Application ID; used to register /verify
DISCORD_BOT_TOKEN Used by setup scripts and at runtime to assign the role and post the welcome message
DISCORD_PUBLIC_KEY Verifies interaction signatures; mounting /api/discord is gated on it
DISCORD_GUILD_ID Guild to register the command in and assign the role in
DISCORD_ROLE_ID The role the bot assigns
DISCORD_START_HERE_CHANNEL_ID Channel for the welcome + button post
DISCORD_START_HERE_MESSAGE_ID Optional; set after the first post so re-runs edit in place instead of posting again

One-time scripts (run from be/ in a clone of Scenarix/jadu-auth)

# Registers the /verify slash command in DISCORD_GUILD_ID (guild-scoped = instant; global takes up to 1h)
npm run discord:register-commands

# Posts the welcome message + "Verify your account" / "Create Account" buttons into #start-here.
# Logs the message id — put it in DISCORD_START_HERE_MESSAGE_ID to edit instead of re-post next time.
npm run discord:post-welcome

The #start-here channel

#start-here is the landing channel every new member sees. It holds a single bot-authored welcome message posted by discord:post-welcome (buttons only exist on bot messages, so it cannot be typed in the client). The post contains:

  • A channel directory grouped as Start Here (#rules, #announcements), Community (#general, #introductions, #tips-and-workflows, #help) and JaduQuest, available once verified (#jaduquest-updates, #creator-showcase, #q-and-a).
  • A link to the StudioJadu YouTube channel.
  • A Get Verified blurb followed by two buttons: Verify your account (opens the email modal, same flow as /verify) and Create Account (link button to apply.studiojadu.com, no interaction sent to our server).

To change the text or add channels, edit be/scripts/postDiscordVerifyWelcome.ts and re-run the script with DISCORD_START_HERE_MESSAGE_ID set so the existing message is edited in place. Channel mentions must use <#channel_id> syntax; plain #rules does not link. Buttons always render below all text, so ordering is controlled purely by the text lines.

Verify

Type /verify in the server or click the button in #start-here, enter your Jadu email, enter the OTP. You should see an ephemeral "✅ Verified!" and gain the role. If the role grant 403s, check the role hierarchy (step 2 above).


2. Discord → Slack relay

Relays messages from selected Discord channels into a Slack channel so the team doesn't have to watch Discord. Built as a Cloudflare Worker with a Cron Trigger, not as an n8n workflow.

How it works

  • Cron */5 * * * * (every 5 minutes).
  • For each channel in DISCORD_CHANNEL_IDS (comma-separated), calls GET /channels/{id}/messages?after=<last_seen_id> with the bot token.
  • Stores the last-seen message ID per channel in a Cloudflare KV namespace so nothing is relayed twice.
  • Posts each new message to a Slack Incoming Webhook, labelled with a friendly name from a CHANNEL_NAMES lookup maintained in the code.
  • Skips bot-authored messages; handles image/attachment-only messages.

Setup

  1. Bot must be in the server with View Channels and Read Message History on each relayed channel (the shared app's bot already is).
  2. Create a Slack Incoming Webhook for the target channel.
  3. Create a KV namespace and bind it in wrangler.toml; add a [triggers] crons = ["*/5 * * * *"] block.
  4. Set DISCORD_CHANNEL_IDS (vars) and DISCORD_BOT_TOKEN, SLACK_WEBHOOK_URL (secrets).
  5. Fill in CHANNEL_NAMES with the real channel names (placeholders were left in the first version).
  6. npx wrangler deploy, then confirm messages arrive in Slack after the next cron tick.

What did not work (don't retry these)

  • n8n native Discord trigger: does not exist. Confirmed against the n8n source repo.
  • n8n-nodes-discord-trigger community node: long credential/ID debugging chain ending in a "Workflow could not be published" error, which is a known unresolved bug in that package.
  • "n8n trigger" app from the Discord App Directory: only relays its own /setup slash-command confirmations, never real chat messages.
  • Root cause: real-time Discord triggers need a persistent Gateway (WebSocket) connection, which neither n8n nor serverless platforms hold cleanly. Polling the REST API on a schedule sidesteps this entirely.

A brief duplicate-message issue during testing was caused by manually hitting the test URL alongside the cron schedule; it does not occur under cron alone. The abandoned n8n files (discord-poll-to-slack.json, discord-to-n8n-bridge.js) are safe to delete.


3. Content drafts pipeline (n8n)

Daily n8n workflow on n8n-internal.scenarix.ai that fetches StudioJadu's recent YouTube / X / Instagram / TikTok posts, has Claude (via Bedrock, forced tool use for structured output) draft 0–5 Discord posts for #announcements, #general, #tips-and-workflows and #jaduquest-updates, and sends them to Slack for human approval. Posting to Discord is deliberately manual; nothing auto-posts.

Workflow export: ~/Documents/scenarix/n8n_flows/StudioJadu Discord Content Drafts.json (latest: ..._21sep.json). Remaining placeholders at last check: X numeric user ID, Instagram user ID, Slack review channel ID, and the StudioJadu voice line in the Build Claude Request node.


Server structure reference

Channel IDs currently hard-coded in the welcome post (postDiscordVerifyWelcome.ts):

Channel ID
#start-here DISCORD_START_HERE_CHANNEL_ID in the auth backend env
#rules 1483871928132571267
#announcements 1483874817819021538
#general 1483875350743089203
#introductions 1544292038080594031
#tips-and-workflows 1542088468325339187
#help (forum) 1483876031889801346
#jaduquest-updates 1542088702556242021
#creator-showcase 1483875492598907032
#q-and-a 1483875901924970777

Other community tooling decided on 2026-08-26: native Discord features first (Community mode, AutoMod, Onboarding, native polls), Carl-bot for moderation. The native Linear Discord integration (/linear issue) is restricted to the admin role under Server Settings → Integrations; the planned form bot (see TODO below) is the intended path for creators.

Troubleshooting

Symptom Likely cause
Interactions Endpoint URL won't save Worker/server not reachable, or DISCORD_PUBLIC_KEY belongs to a different app
Role grant returns 403 Bot's highest role is below the target role in Server Settings → Roles
/verify not appearing discord:register-commands not run for this guild, or wrong DISCORD_GUILD_ID
/api/discord returns 404 DISCORD_PUBLIC_KEY unset on the auth backend, so the router isn't mounted
Slack relay duplicates Something besides cron is invoking the poll (e.g. manual test URL)

TODO: Feedback → Linear bot

Not set up yet (as of 2026-09-23)

The code is written and unit-tested but exists only as an uncommitted discord-worker/ folder in a local checkout of Scenarix/auto-pr. It is not on GitHub and nothing is deployed. First step is to commit and push it; then the README inside it becomes visible in this portal under auto-pr.

Intent: creators click 🐛 Report a bug or 💡 Feature request on a pinned post in the #feedback forum channel, fill a native Discord form, and a Linear issue (team PEA) plus a public forum thread are created. When the issue is marked Done (or Canceled) in Linear, the bot posts back into the thread and applies the ✅ Shipped tag.

Design points already decided:

  • No gateway, no database. Discord POSTs to the Worker's Interactions Endpoint URL; the issue↔thread mapping is a Discord Thread: <url> line the Worker writes into every Linear issue description (attachment as fallback).
  • Bot permissions needed: View Channels, Send Messages, Send Messages in Threads, Create Posts, Manage Threads.
  • Non-secret config goes in [vars] in wrangler.toml: DISCORD_APPLICATION_ID, FEEDBACK_CHANNEL_ID, DISCORD_TAG_ID_BUG / _FEATURE / _SHIPPED, LINEAR_TEAM_ID, LINEAR_LABEL_ID_DISCORD / _BUG / _FEATURE, plus switches ANNOUNCE_CANCEL, APPLY_SHIPPED_TAG, COOLDOWN_SECONDS.
  • Secrets via npx wrangler secret put: DISCORD_PUBLIC_KEY, DISCORD_BOT_TOKEN, LINEAR_API_KEY (write scope), LINEAR_DISCORD_WEBHOOK_SECRET.
  • Deployment manual (no CI workflow), deliberately.

Open question: a Discord application has only one Interactions Endpoint URL, and the auth backend already owns it for /verify. Either give this worker its own Discord application, or route /interactions through one endpoint that dispatches on custom_id. Decide before step 4 below.

Remaining steps:

  1. Commit and push discord-worker/ to Scenarix/auto-pr.
  2. Collect IDs: #feedback channel ID, forum tag IDs (not right-clickable; fetch with the bot token):

    curl -s -H "Authorization: Bot $DISCORD_BOT_TOKEN" \
      https://discord.com/api/v10/channels/$FEEDBACK_CHANNEL_ID | jq '.available_tags[] | {id, name}'
    

    Plus Linear team ID and label IDs (discord, bug, feature-request in team PEA), and a Linear API key with write scope. 3. Fill [vars] in wrangler.toml, then npm ci && npm test && npx wrangler deploy. Set the three Discord/Linear secrets. 4. Pin the START HERE post: DISCORD_BOT_TOKEN=... FEEDBACK_CHANNEL_ID=... node scripts/pin-feedback-buttons.js. Re-running creates a duplicate; delete the old post first. 5. Developer Portal → General Information → Interactions Endpoint URL → https://discord-worker.<subdomain>.workers.dev/interactions (see open question above). 6. Linear Settings → Webhooks → create (Issues only, team PEA) pointing at https://discord-worker.<subdomain>.workers.dev/webhook/linear/discord; put its signing secret in LINEAR_DISCORD_WEBHOOK_SECRET. 7. End-to-end: submit a bug from the pinned post, confirm the Linear issue + forum thread, move the issue to Done, confirm "🎉 This has shipped!" in the thread. Watch npx wrangler tail.