Administrator reviewing a topic list on one monitor and a simple chat-channel sidebar on the other

Discourse does not ship a Discord server. It ships four different doors people mash together and then call “the Discord integration.” Only one of them posts “new topic in Announcements” into a guild channel. None of them is a two-way mirror of a Discord guild. None of them is the XenForo [8WR] Discord Integration add-on.

The XenForo Discord article owns stock XF webhooks, connected-account patterns, and [8WR] role sync. This article does not reprint that tour. Discourse’s official pieces are:

  1. discourse-chat-integration — outbound notifications to Slack, Telegram, Discord, Mattermost, Matrix, Zulip, Rocket.Chat, and friends.
  2. Discourse Chat — an in-app chat product (channels, DMs). Not Discord.
  3. Discord login — listed on hosted authentication. Identity, not chat.
  4. Core webhooks (Meta 49045) plus DiscourseConnect — generic event pipes and SSO. Discord is one possible receiver, not the point of the protocol.

Pick one job, ship it, measure it. A site that requires Discord login, mirrors every post, embeds Chat, and fires raw webhooks at the same guild has four products and one outage.

Official sources: Meta’s Chat Integration topic (66522), the Discord provider how-to (66600), the Chat plugin page, Meta webhooks (49045), DiscourseConnect (13045), and the 2026 pricing/auth grid. Setting names below are the ones those pages print. This article does not invent extra ones.

Four jobs, four tools

Job Official Discourse tool Two-way with Discord?
Announce new public topics / posts in a Discord channel Chat Integration (Discord provider) No. Outbound only. Discord has notifications, not slash commands
Live chat on the forum Discourse Chat plugin No. Different product. Hosted: all plans
Log in / register with a Discord account Discord on the hosted auth list (self-host: Meta’s Discord login how-to) Login only. Not roles, not nicknames
Tell an arbitrary HTTP endpoint that a topic was created Core webhooks (49045) No. You write the receiver. Discord incoming webhooks want Discord-shaped JSON
Parent site owns every login DiscourseConnect (hosted Business lists SSO / OIDC / OAuth2) Not Discord-specific
Map Discourse groups to Discord roles; mirror bans Not in the official chat-integration page Community bots exist. They are not this article

If you only need the first row, stop after the Chat Integration section. If you wanted [8WR]-shaped role sync, you are still on the XenForo article — or you are buying a bot that is not official Discourse.

Chat Integration is outbound notifications

Meta’s plugin topic opens with the sentence people skip: Chat Integration is for using an external chat system together with Discourse. If you wanted chat inside Discourse, that is Discourse Chat.

Official summary of what the plugin sends:

  • New topics
  • New replies
  • Messages to a group
  • Mentions of a group

It is not “Discord messages become Discourse posts.” Slack has extra toys (slash commands, transcripts, threaded replies). Discord does not. The provider matrix on Meta:

Provider Notifications Slash commands Transcripts Threaded replies
Slack Yes Yes Yes Yes
Telegram Yes Yes
Discord Yes
Mattermost Yes Yes
Matrix Yes
Zulip Yes
Rocket.Chat Yes
Microsoft Teams / Google Chat / Webex / others Notifications (see Meta for the current tick) Usually no

Hosted: Chat Integration is listed on Pro, Business, and Enterprise. Self-host: the plugin is official and, on current Discourse, included — the Discord how-to says no separate install is required. If your hosted admin UI has no Plugins section, you are on a plan or role that does not expose it; that is a plan question, not a missing zip file.

Three nouns: provider, channel, rule

  1. Provider — the chat platform (Discord, Slack, …).
  2. Channel — one Discord channel (one incoming webhook URL). A provider can have many channels.
  3. Rule — when Discourse should send a message to that channel. A channel can have many rules.

Admin path the official pages name:

  1. Enable chat integration enabled.
  2. Enable the provider: chat integration discord enabled.
  3. Plugins → Chat Integrations → Discord → Create Channel.
  4. Create rules on that channel.

Discord: webhook on the guild, then the channel row

Official Discord how-to (administrator):

On Discord

  1. Server name → Server Settings → Integrations → Webhooks → Create Webhook.
  2. Name, icon, destination channel.
  3. Copy the webhook URL. Save.

URL shapes Discord actually issues:

  • Standard: https://discord.com/api/webhooks/…
  • PTB: https://ptb.discord.com/api/webhooks/…
  • Canary: https://canary.discord.com/api/webhooks/…

If you are on PTB or Canary, strip ptb. or canary. before you paste into Discourse. The plugin expects the standard host.

Treat that URL like a password. Anyone who has it can post into the channel until you rotate the webhook.

On Discourse

  1. chat integration enabled and chat integration discord enabled on.
  2. Create Channel: a display name (admin-only) + the webhook URL.
  3. Test. You should see a test message in Discord before you write rules.
  4. Then add rules. Do not skip Test. A 400 from Discord is a bad body or a dead URL, not “Discourse is down.”

Rules that will not flood the guild

The official rule form:

Type

  • Normal — topics in a selected category (or all categories).
  • Group message — a PM to a selected group.
  • Group mention — the group was mentioned.

Filter

  • All posts with threaded replies — not available for every provider. Discord is not in the threaded-replies column.
  • All posts and replies
  • First post only
  • Tag added to topic — historically tied to a small-action setting; Meta later moved tag-change notifications toward Automation scripts. If your site still shows the filter, use the label you see. If it does not, look at Automation rather than inventing a checkbox.
  • Mute — carve an exception out of a broader rule.

Optional: category, tags (topic must have at least one of the listed tags), group when the type is a group event.

When multiple rules match, the plugin uses a documented order: group mentions, then group messages, then normal topics; and inside each type, Mute beats “all posts,” which beats “first post only.” The first matching rule wins. Use Mute. Do not create twelve overlapping “all categories” rules and hope.

A technical community that already has Support + Docs + Announcements can start with three Discord channels:

Discord channel Rule
#forum-announcements Normal → Announcements → First post only
#forum-support Normal → Support → First post only (or all posts if the channel is staff-only)
#forum-staff Group message / group mention for @staff, or Normal → Staff category, on a private Discord channel

Do not point “all categories, all posts” at a public Discord. Restricted Discourse categories will leak. The payload is built as the system user by default, and system can see everything.

Security: change the Discourse username

Meta is explicit. Default sender is system, which means no restriction on what may be notified.

Create a non-staff user with only the category access you are willing to print on a billboard. Put that username in chat integration discourse username.

If you ever enable slash commands on a provider that has them (Slack, Telegram, Mattermost), and you do not trust every member of that Slack workspace, this username is absolutely essential. Discord has no slash commands in the official matrix — the leak path on Discord is still “system can see Staff.” Fix the username anyway.

Two webhooks (public channel + private staff channel) with two different Discourse users is cleaner than one user and a prayer.

Optional Discord flavour: chat_integration_discord_message_content

The Discord how-to names this site setting. It prepends text to every notification. Official uses:

  • Custom text
  • Mention a role: <@&ROLE_ID>
  • Mention a user: <@USER_ID>
  • @everyone if you are willing to live with the consequences

Role IDs: Discord Developer Mode → Server Settings → Roles → Copy Role ID. Test after you save. An @here on every Support reply is how people mute the channel and then miss the incident post.

This setting is not a template language for embeds. Do not invent Mustache fields. The plugin builds the embed. You add a prefix.

Discourse Chat is a different plugin

Discourse Chat adds in-app realtime chat: public channels that follow your existing category permissions, threads, direct and group messages, notifications, search. Official hosted page: available on all hosting plans. Self-host: official plugin (core-adjacent; Meta 230881).

It is the right tool when:

  • You want short-form chat without sending people to Discord.
  • You want channel visibility to match Support / Staff / Internal.
  • You want one identity (the Discourse user) in both topics and chat.

It is the wrong tool when:

  • The community already lives in a Discord guild and will not open a second chat product.
  • You thought Chat Integration “put Discord inside the forum.” It did not.

Trust-level chat rate limits still apply to new users (TL0: 20 messages / 30 seconds; TL1: 40 / 30s — those defaults live in the trust-level notes, not in the Chat marketing page). Do not turn the sandbox off because chat felt slow for a brand-new account.

You can run Chat and Chat Integration at the same time. Typical pattern: in-app Chat for people who stay on the site; Discord notifications for people who refuse to leave the guild. That is two outbound cultures, not a sync.

There is no official “Discord channel ↔ Discourse Chat channel” bridge in the pages this article is allowed to use. Third-party bridges exist in the universe. They are not a Meta install guide. If you need that, you are in custom-bot territory.

Discord login is identity, not chat

The 2026 hosted pricing page lists Discord under Authentication, alongside Google, GitHub, Facebook, Twitter, Apple, Discourse ID, DiscourseConnect, OIDC/OAuth2 (Business), SAML (extra cost). That list is login. It does not sync guild roles. It does not post topics.

Self-host: follow Meta’s Configure Discord Login for Discourse topic end-to-end. This article will not paraphrase an OAuth client-id dance from memory. The XenForo article’s general lesson still applies as operations: create an application at the Discord Developer Portal, restrict scopes to what login needs, do not tick every bot scope “to be safe.”

When Discord login is enough:

  • You want fewer passwords.
  • Members already have Discord accounts.
  • You are not trying to make the guild the source of truth for moderation.

When it is not enough:

  • The parent product already owns accounts — that is DiscourseConnect or OIDC, not Discord OAuth.
  • You need “banned on the forum → banned in the guild.” Official Chat Integration will not do that.

Hosted Free/Pro can use the social logins the plan lists. Custom OIDC/OAuth2 and DiscourseConnect are on the Business row of the public grid (re-check live pricing). Self-host can enable what you install. Do not enable Discord login and DiscourseConnect that also sends Discord as a second personality unless you like duplicate users.

Core webhooks (Meta 49045): when Chat Integration is the wrong shape

Chat Integration is opinionated: category/tag rules → a chat provider. Core webhooks are generic: Discourse POSTs a JSON (or form-urlencoded) payload to a URL you trust when an event fires.

Use webhooks when:

  • The receiver is not a supported chat provider (your CRM, a custom bot, GitHub, Zapier, n8n).
  • You need topic_created / post_created / user_created in your schema, not a Discord embed.
  • You are willing to verify signatures and filter events.

Do not paste a Discord incoming-webhook URL into core webhooks and expect a pretty embed. Discord wants Discord’s body (content, embeds). Discourse will send Discourse’s event JSON. That mismatch is the same one XenForo operators hit with stock XF webhooks. Chat Integration exists so you do not write that mapper for Slack/Discord/Telegram.

What the official how-to actually tells you to do

  1. Admin sidebar search: webhooks.
  2. Add Webhook.
  3. Payload URL — HTTPS strongly recommended. Trust this URL. The payload is generated as system and may include staff-only content (for example a reply to a message).
  4. Content type: application/json (preferred) or application/x-www-form-urlencoded.
  5. Optional secret → header X-Discourse-Event-Signature (sha256= + HMAC of the raw body). Set one. Verify it.
  6. Select individual events or Send me everything (includes event types added later).
  7. Optional filters: categories, tags, groups.
  8. Check TLS certificates. Mark active. Create.

Headers the topic tells receivers to read:

  • X-Discourse-Event-Id
  • X-Discourse-Event-Type
  • X-Discourse-Event (internal hook name)
  • X-Discourse-Event-Signature

Event types the wiki still spells out (the UI has grown — use what you see):

Type Example events
Topic topic_created, topic_destroyed, topic_recovered (can restrict by category)
Post post_created, post_destroyed, post_recovered, validate_post
User user_created, user_approved

Ping sends an empty event so you can test the URL without a real post. Delivery status lives on the webhook: Inactive, Disabled, Successful, Failed. You can redeliver failures.

If you need Discord and you insist on core webhooks, put n8n / Zapier / a tiny worker between them: verify HMAC → map title + URL into content → POST to Discord. That is a fine architecture. It is not simpler than Chat Integration for the “new Announcements topic” job.

DiscourseConnect: SSO, not a Discord feature

DiscourseConnect is official SSO. It replaces Discourse authentication when enabled: login redirects to discourse_connect_url with a signed payload; local password and social logins go away. Hosted marketing lists it on paid plans (the Connect topic says offered to Pro/Business/Enterprise; the 2026 pricing grid puts SSO OIDC/OAuth2 on Businessre-check the live page and do not conflate DiscourseConnect with the OIDC plugin).

Three settings the official topic names:

  • enable_discourse_connect
  • discourse_connect_url
  • discourse_connect_secret

Payload fields that matter for staff (see also the technical community SSO table): nonce, verified email, external_id, optional username, name, admin, moderator, groups / add_groups / remove_groups. Validate emails before you send them. If you cannot, require_activation=true — and you are still taking a risk the topic tells you not to take.

DiscourseConnect is how a product site owns identity. Discord login is how a Discord-native crowd skips a password. They are different doors. Turning on Connect disables the Discord login door.

If you check enable_discourse_connect by mistake, Meta has a “locked yourself out” topic. Read it before you enable SSO on production. The beginner’s guide already said: week one is the wrong time.

/admin/users/sync_sso can push updates later (admin API key). Logout can POST /admin/users/{id}/log_out. This article will not paste a full HMAC walkthrough — the official topic has the reference implementation. Use it.

Worked patterns (pick one)

A. Public product, Discord as a loudspeaker

  • Chat Integration → #announcements, first post only, Announcements category.
  • chat integration discourse username = a user who can see only public categories.
  • No Discord login required.
  • Discourse Chat optional for people who stay on the site.

B. Support team already lives in Discord

  • Chat Integration → private #forum-support, Support category, all posts or first post only.
  • Staff category → a private Discord channel, different webhook, username that can see Staff.
  • Solved still happens on Discourse (technical community). Discord is the pager, not the archive.
  • Do not accept answers in Discord and leave the topic unsolved.

C. Login with Discord, conversation on Discourse

  • Enable Discord authentication. Stop.
  • No Chat Integration until someone misses announcements.
  • Do not grant forum admin to everyone with a Discord nitro badge.

D. Parent app owns users

  • DiscourseConnect or hosted OIDC (Business).
  • Discord becomes a Chat Integration loudspeaker at most.
  • Do not also enable Discord login “for convenience.”

E. You thought you wanted a two-way guild sync

  • Official tools will not give you that.
  • Stay on XenForo + [8WR] if role sync is load-bearing, or budget a custom bot and a person who will keep it alive through Discord API changes.
  • Be honest in the stay-or-migrate sense: this is a product gap, not a missing checkbox.

Checklist

Before you enable anything

  • You can name the one job
  • You know whether Discord is a loudspeaker, a login, or a place staff already work
  • You have read the XF Discord piece only to confirm you are not trying to install [8WR] on Discourse

Chat Integration

  • chat integration enabled + chat integration discord enabled
  • Webhook URL is the discord.com form (PTB/Canary stripped)
  • Test message arrived
  • Rules are first-post-only unless the channel is private and staffed
  • chat integration discourse username is not system if any category is private
  • chat_integration_discord_message_content does not @everyone on every reply

Chat plugin

  • You want in-app chat, not Discord-in-a-box
  • Channel permissions match categories
  • You did not disable the TL0 chat rate limit to make a demo look busier

Login / SSO

  • Discord login or DiscourseConnect / OIDC, not a pile of both
  • Connect secret is not in a public repo
  • Emails are verified before they hit Connect

Core webhooks

  • HTTPS + secret + signature check
  • Individual events, not “everything,” unless you mean it
  • You did not point them at Discord’s incoming webhook URL raw

What not to do

  • Do not call Chat Integration a two-way Discord bridge. Meta already printed the warning.
  • Do not confuse Discourse Chat with Chat Integration. The names are a gift to confusion.
  • Do not notify all categories as system into a public guild.
  • Do not paste Discord webhook URLs into core webhooks and wonder why the embed is ugly.
  • Do not enable slash commands on Slack without the locked-down Discourse username.
  • Do not use DiscourseConnect to “add Discord.” Connect replaces login.
  • Do not expect official group→role sync. That is the XF add-on story.
  • Do not rebuild during a Discord outage and blame Discourse (or the reverse). These are separate availability domains.

Takeaways

  • Chat Integration posts Discourse activity out to Slack, Telegram, Discord, Mattermost, Matrix, Zulip, Rocket.Chat, and others. Discord gets notifications only — no slash commands, no transcripts, no threads. Hosted: Pro+.
  • Configure provider → channel (webhook URL) → rules. Prefer first post only on public channels. Set chat integration discourse username to a user who cannot see Staff.
  • chat_integration_discord_message_content can mention Discord roles as <@&id>. Use it sparingly.
  • Discourse Chat is in-app chat that follows category permissions. It is not Discord. Hosted: all plans.
  • Discord login is on the hosted auth list. It does not sync roles.
  • Core webhooks (Meta 49045) are a generic signed POST. Verify X-Discourse-Event-Signature. Do not feed them raw to Discord.
  • DiscourseConnect is official SSO and replaces other login methods. Map staff groups there; do not pile it on top of Discord OAuth without a story.
  • None of this is the XenForo Discord add-on. If you need that add-on’s job, you need that add-on — or a bot you will maintain.

If #forum-announcements gets the first post, Staff stays off the public webhook, and login is one door, you have a Discourse–Discord integration. Everything else is a second product. Ship the first one.