Deploying Rapidflare
Integrating Rapidflare AI Agent with your Discourse forum
This guide will help you connect Rapidflare to your Discourse community forum. This is an admin flow.
There are two ways to add the agent to your forum, and they can run side by side:
| Option | What members see |
|---|---|
| Replies in topics | The agent answers new topics, and replies that mention it, as a forum member |
| Chat page | An Ask AI button or a full-page chat that members open directly |
Prerequisites
- Discourse administrator access to your forum
- Access to the Rapidflare Admin Dashboard
- A Discourse account for the agent to post as (see Step 1)
How it works
When someone opens a topic in a category you have chosen, Discourse notifies Rapidflare, the agent answers from your knowledge base, and the reply is posted in that topic. Readers continue the conversation by mentioning the agent, and it remembers the earlier turns in that topic.
Unlike Slack and Discord, there is no app to install. You create an account for the agent on your own forum, issue it an API key, and paste a webhook address into your Discourse settings.
Step 1: Create the agent's account
The agent posts as a normal Discourse user, so it needs an account.
- In your forum, go to Admin → Users and invite a new user (for example,
acme-assistant). - Choose a name your community will recognize as the assistant. This name is what people will type to call it.
- Open the new account in Admin → Users and set Trust Level to 4 (Leader).
Set trust level 4
New accounts start at trust level 0, which limits how often they can post and how many links they can include. An agent that answers regularly will be rate-limited or flagged as spam without trust level 4.
Step 2: Create an API key
- Go to Admin → Advanced → API keys and click New API Key.
- Description — something that names the integration, so a future admin knows what the key is for.
- User level — change this from All users to Single User, then choose the account from Step 1.
- Scope — Global works. If you prefer a narrower key, use Granular and allow creating and deleting posts.
- Save, then copy the key. Discourse shows it only once.
Change the user level
User level defaults to All users, which lets the key act as anyone on your forum, including staff. The agent only ever needs to post as itself, so set this to Single User.
Step 3: Connect from the Rapidflare dashboard
- In the Rapidflare Admin Dashboard, open your hub and go to Deployments.
- Click the Discourse row.
- Fill in:
- Forum URL — for example
https://forum.example.com - Bot username — the account from Step 1
- API key — the key from Step 2
- Forum URL — for example
- Click Connect.
Rapidflare checks the key against your forum and then shows you two values: a Payload URL and a Secret. Copy both — the secret is shown only once.
Step 4: Register the webhook in Discourse
- Back in your forum, go to Admin → Webhooks and click Add webhook.
- Payload URL — paste the URL from Step 3.
- Content Type — leave it as
application/json. - Secret — paste the secret from Step 3.
- Under Which events should trigger this webhook?, choose Select individual events and tick Post is created under Post Events. Leave Send me everything alone — Rapidflare ignores every event except a created post.
- Under Triggered Categories, add every category the agent should answer in.
- Tick Active, then click Create.
Triggered Categories is what scopes the agent
This is the only place scope is set. Name categories and the agent answers in those alone; leave it blank and the forum sends every category, so the agent would answer in announcements and staff areas too.
Tick Active, or nothing is sent
Active sits at the bottom of a long form and is easy to scroll past. A webhook saved without it is created but never fires, and the delivery log stays empty — so there is nothing to tell you it is off.
Step 5: Try it
Post a new topic in one of your chosen categories, using an account other than the agent's. The agent posts once, when its answer is ready, so expect nothing in the topic until the reply itself appears.
To continue the conversation, reply to the topic and mention the agent by name:
@acme-assistant does this apply to version 6 as well?
Where the agent answers
Scope lives on the webhook, in Triggered Categories, and nowhere else. Rapidflare answers wherever your forum delivers a post, so changing which categories the agent covers means editing the webhook — there is no second list to keep in step.
The mention rule is separate and always applies: only the opening post of a topic is answered unprompted, and later posts have to mention the agent.
How the agent decides to answer
| Situation | What happens |
|---|---|
| New topic in a category the webhook covers | Answered automatically |
| New topic in any other category | Never reaches Rapidflare |
| Reply that mentions the agent | Answered, continuing the topic's conversation |
| Reply that does not mention the agent | Ignored, so the agent stays out of discussions between people |
| The agent's own posts | Ignored |
Changing your settings
Open the Discourse row in Deployments again to change the forum URL or the bot account. To change which categories the agent covers, edit Triggered Categories on the webhook instead.
Re-enter the API key to save changes
Rapidflare never displays a stored API key, so changing any setting means entering the key again. Your webhook secret is reused, so the webhook you registered in Step 4 keeps working.
Disconnecting
Click the Discourse row and confirm Disable. Rapidflare stops answering immediately and erases the stored credentials. You can then delete the webhook and revoke the API key in your forum.
Troubleshooting
The agent does not reply to a new topic. Work down the path the post takes:
- Is the webhook Active? An inactive webhook has an empty delivery log — check Admin → Webhooks.
- Did the forum send it? Open the webhook and look at its delivery log. Nothing listed means Triggered Categories excludes that category — add it there.
- Did Rapidflare accept it? A delivery showing
200was received. Anything else is covered below. - Who posted it? The agent never answers its own posts.
Replies are ignored. Only replies that mention the agent are answered. Check the spelling of the username — @acme-assistant2 is a different account, not a mention of @acme-assistant.
Replies appear but get flagged or hidden. The agent's account is probably below trust level 4. Raise it in Admin → Users.
The agent stopped mid-conversation. If a moderator moved the topic to a category that is not on your list, the agent stops answering there. Move it back, or add that category.
Discourse shows webhook errors. Open Admin → Webhooks, select the webhook, and read the delivery log. Redeliver replays a failed event once you have fixed the cause, so you can confirm without posting again.
| Status | Meaning |
|---|---|
200 | Received. If the agent still said nothing, the response body gives the reason — not_mentioned or duplicate. |
401 | The secret does not match. Reconnect from the dashboard for a fresh pair and update it here. |
404 | The payload URL is wrong, or the Discourse deployment was disabled in Rapidflare. |
Adding a chat page
Instead of, or alongside, replies in topics, you can give members a chat they open directly: a floating Ask AI button, or a dedicated full-page chat. It uses the same Embed SDK as our web widget, loaded by a Discourse theme component, so the chat interface, its answers and its citations are the same as on any other website. Discourse only hosts the page.
Prerequisites
- Discourse administrator access to your forum
- A Discourse plan that allows custom theme components
- Access to the Rapidflare Admin Dashboard
- A publishable API key for your hub (see Create a publishable API key)
Choose a layout
| Layout | What members see | What you install |
|---|---|---|
| Floating button | An Ask AI button on every page that opens a chat panel, with chat history | Code pasted into one tab of a theme component |
| Full-page chat | A dedicated page, for example /ask, where the whole page is the chat | A small theme component uploaded as a zip |
Create a publishable API key
- In the Rapidflare Admin Dashboard, open your hub and go to Configure » Security.
- Click Create key and keep the type on Publishable.
- Give the key a name.
- Under Allowed domains, type your forum's hostname (for example
forum.example.com) and press Enter or comma. It becomes a chip in the field. At least one domain is required. - Choose an expiry, click Create key, and copy the key. The dashboard shows it only once.
Check the allowed domain
The key works only on the domains you list. If the chat shows an error, open the key's Allowed domains in the dashboard and confirm it matches the exact hostname your forum is opened on. See API keys.
A publishable key is meant to be visible in the browser, so it is safe inside a theme component. Never use a secret key here: theme code is sent to every visitor of your forum.
Why the standard snippet does not work on Discourse
Our web instructions add the loader with a <script src="…"> tag. Discourse's content security policy blocks such a tag, so on a Discourse forum nothing appears and the browser console shows a script-src error.
Load the SDK from the theme component's JS instead, with Discourse's own loadScript. Scripts loaded that way are trusted, and so is everything the SDK loads after them. You do not need to change any Discourse security setting.
Floating button
Create the theme component
- In your forum, go to Admin → Appearance → Themes & components and open the Components tab.
- Click Install, choose Create new, set Type to Component, name it (for example
Rapidflare Agent), and click Create. - Under Include component on these themes, add the theme your forum uses and save. A component that is not included on the active theme does nothing.
Add the code
- Click Edit Code, and open the JS tab (under Common). Do not use the CSS or <head> tabs.
- Replace everything in the tab with the code below, put your publishable key in
RAPIDFLARE_API_KEY, and click Save.
import { apiInitializer } from "discourse/lib/api";
import loadScript from "discourse/lib/load-script";
const RAPIDFLARE_API_KEY = "YOUR_PUBLISHABLE_API_KEY";
const RAPIDFLARE_AGENT_JS_URL =
"https://cloud.rapidflare.ai/storage/v1/object/public/rapidflare-embed-sdk/latest/agent.js";
export default apiInitializer((api) => {
const currentUser = api.getCurrentUser();
shieldWidgetTypingFromDiscourseShortcuts();
let launcher = null;
loadScript(RAPIDFLARE_AGENT_JS_URL)
.then(() => {
launcher = window.rapidflare.init({
id: "discourse-rapidflare-launcher",
apiKey: RAPIDFLARE_API_KEY,
theme: currentTheme(),
...(currentUser ? { userId: currentUser.username } : {}),
// bottom-left keeps the button clear of Discourse's chat drawer.
display: { type: "floating-launcher", position: "bottom-left", titleText: "Ask AI" },
});
})
.catch((error) => console.error("[Rapidflare] setup failed:", error));
// Discourse changes pages without reloading; keep the agent's page context current.
api.onPageChange((url, title) => {
try {
launcher?.update({
context: { referrerPage: { url: new URL(url, window.location.origin).href, title } },
});
} catch {
// The launcher is not ready yet.
}
});
});
function currentTheme() {
const scheme = getComputedStyle(document.documentElement).getPropertyValue("--scheme-type").trim();
return scheme === "dark" ? "dark" : "light";
}
// See "Typing in the chat" below.
function shieldWidgetTypingFromDiscourseShortcuts() {
const stopIfTypingInWidget = (event) => {
if (event.key === "Escape") {
return;
}
const path = event.composedPath();
const target = path[0];
const typing = target?.isContentEditable || ["INPUT", "TEXTAREA", "SELECT"].includes(target?.tagName);
if (typing && path.some((node) => node?.tagName === "RAPIDFLARE-AGENT")) {
event.stopPropagation();
}
};
["keydown", "keypress", "keyup"].forEach((type) => document.body.addEventListener(type, stopIfTypingInWidget));
}
Hard-refresh a forum page (Ctrl+Shift+R). The Ask AI button appears in the bottom-left corner.
Full-page chat
A full-page chat needs its own address on your forum, such as https://forum.example.com/ask. Adding an address takes several files, which the admin code editor cannot create, so this layout is installed as a zip.
Files
Create a folder with these files, then zip its contents (the files, not the folder around them):
about.json
settings.yml
common/common.scss
common/head_tag.html
javascripts/discourse/rapidflare-ask-route-map.js
javascripts/discourse/routes/rapidflare-ask.js
javascripts/discourse/templates/rapidflare-ask.gjs
javascripts/discourse/lib/rapidflare-agent.js
javascripts/discourse/api-initializers/rapidflare-ask.js
about.json
{
"name": "Rapidflare Chat Page",
"component": true
}
settings.yml (your key is entered after upload, in the component's Settings)
rapidflare_api_key:
type: string
default: ""
rapidflare_agent_js_url:
type: string
default: "https://cloud.rapidflare.ai/storage/v1/object/public/rapidflare-embed-sdk/latest/agent.js"
page_title:
type: string
default: "Ask AI"
javascripts/discourse/rapidflare-ask-route-map.js registers the address:
export default function () {
this.route("rapidflare-ask", { path: "/ask" });
}
javascripts/discourse/lib/rapidflare-agent.js mounts and removes the chat:
import loadScript from "discourse/lib/load-script";
import User from "discourse/models/user";
let agent = null;
export async function mountAgent(element) {
await loadScript(settings.rapidflare_agent_js_url);
unmountAgent();
const currentUser = User.current();
try {
agent = window.rapidflare.init({
apiKey: settings.rapidflare_api_key,
theme: currentTheme(),
...(currentUser ? { userId: currentUser.username } : {}),
display: { type: "full-page", container: element },
});
} catch (error) {
console.error("[Rapidflare] setup failed:", error);
}
}
export function unmountAgent() {
if (!agent) {
return;
}
try {
agent.destroy();
} catch {
// Already removed together with the page.
}
agent = null;
}
function currentTheme() {
const scheme = getComputedStyle(document.documentElement).getPropertyValue("--scheme-type").trim();
return scheme === "dark" ? "dark" : "light";
}
javascripts/discourse/routes/rapidflare-ask.js removes the chat when people leave the page:
import DiscourseRoute from "discourse/routes/discourse";
import { unmountAgent } from "../lib/rapidflare-agent";
export default class RapidflareAskRoute extends DiscourseRoute {
deactivate() {
super.deactivate(...arguments);
// Runs while the chat is still on the page, before Discourse removes it.
unmountAgent();
}
titleToken() {
return settings.page_title;
}
}
javascripts/discourse/templates/rapidflare-ask.gjs is the page itself:
import bodyClass from "discourse/helpers/body-class";
import hideApplicationFooter from "discourse/helpers/hide-application-footer";
import hideApplicationSidebar from "discourse/helpers/hide-application-sidebar";
import { modifier } from "ember-modifier";
import { mountAgent, unmountAgent } from "../lib/rapidflare-agent";
const rapidflareAgent = modifier((element) => {
mountAgent(element);
return () => unmountAgent();
});
export default <template>
{{bodyClass "rapidflare-ask-page"}}
{{hideApplicationSidebar}}
{{hideApplicationFooter}}
<div class="rapidflare-ask" {{rapidflareAgent}}></div>
</template>
javascripts/discourse/api-initializers/rapidflare-ask.js adds a sidebar link and restores the page after a reload:
import { apiInitializer } from "discourse/lib/api";
import getURL from "discourse/lib/get-url";
export default apiInitializer((api) => {
restoreAskPage();
shieldWidgetTypingFromDiscourseShortcuts();
api.addCommunitySectionLink({
name: "rapidflare-ask",
route: "rapidflare-ask",
title: settings.page_title,
text: settings.page_title,
icon: "robot",
});
});
// common/head_tag.html sends a reload of /ask to /?rapidflare_ask; put the address back before routing.
function restoreAskPage() {
const url = new URL(window.location.href);
if (!url.searchParams.has("rapidflare_ask")) {
return;
}
url.searchParams.delete("rapidflare_ask");
url.pathname = getURL("/ask");
window.history.replaceState(window.history.state, "", url.href);
}
// See "Typing in the chat" below.
function shieldWidgetTypingFromDiscourseShortcuts() {
const stopIfTypingInWidget = (event) => {
if (event.key === "Escape") {
return;
}
const path = event.composedPath();
const target = path[0];
const typing = target?.isContentEditable || ["INPUT", "TEXTAREA", "SELECT"].includes(target?.tagName);
if (typing && path.some((node) => node?.tagName === "RAPIDFLARE-AGENT")) {
event.stopPropagation();
}
};
["keydown", "keypress", "keyup"].forEach((type) => document.body.addEventListener(type, stopIfTypingInWidget));
}
common/head_tag.html handles a reload or a shared link. Discourse's server does not know /ask, so it answers with a plain "page not found" that never starts the forum app. This sends the visitor through the homepage, and the initializer above puts /ask back:
<script>
(function () {
if (!document.body.classList.contains("no-ember")) {
return;
}
if (/^\/ask\/?$/.test(window.location.pathname)) {
window.location.replace("/?rapidflare_ask");
}
})();
</script>
common/common.scss makes the chat fill the page below the header:
body.rapidflare-ask-page #main-outlet-wrapper,
body.rapidflare-ask-page #main-outlet-wrapper #main-outlet {
max-width: none;
margin: 0;
padding: 0;
}
.rapidflare-ask {
display: flex;
flex-direction: column;
height: calc(var(--composer-vh, 1vh) * 100 - var(--main-outlet-offset, 0px));
}
To use a different address than /ask, change it in three places: the route map, restoreAskPage() and head_tag.html.
Install the component
- Go to Admin → Appearance → Themes & components → Components.
- Click Install, choose From your device, select the zip, and click Install.
- Under Include component on these themes, add the theme your forum uses and save.
- Open the component's Settings and paste your publishable key into rapidflare_api_key.
- Hard-refresh the forum and open
/ask, or the new link in the sidebar.
Uploading again creates a second component
Each zip upload creates a new component rather than updating the existing one. To change the code, disable or delete the old copy first, upload the new zip, include it on your theme again and re-enter the key. Installing the component From a git repository instead lets you update it in place.
Typing in the chat
Discourse has keyboard shortcuts, and its chat feature adds some that work anywhere on the page, such as - to open the chat drawer. Because the widget runs in its own isolated part of the page, Discourse cannot tell that someone is typing in it, so those keys would open Discourse chat instead of appearing in the message box.
Both code samples above include shieldWidgetTypingFromDiscourseShortcuts(), which stops key presses inside the chat's text box from reaching Discourse. Keep it when you adapt the code.
Troubleshooting
Nothing appears.
- Is the component included on the active theme? Check Include component on these themes.
- Is the code in the JS tab? Code in the CSS or <head> tab does not run as JavaScript.
- Open the browser console. A
script-srcerror means the SDK was added with a<script src>tag instead ofloadScript.
The chat shows an error or answers with a 401. The key is not allowed for this domain, or it is not a publishable key. Check the key's Allowed domains in the dashboard, and make sure the forum is opened on that exact hostname.
Typing - opens Discourse chat. shieldWidgetTypingFromDiscourseShortcuts() is missing from the code.
The forum breaks after saving. Add ?safe_mode=no_themes to any forum address to load it without theme components, then disable the component from the admin page.