How to build a Discord support bot with Unified.to
December 12, 2024
Want to build an AI-powered support bot that can answer questions using an external knowledge base? In this guide, we'll show you how to use Unified.to's KMS and Messaging APIs to create a data pipeline that pulls content from a knowledge management system (like Notion) and train your Discord bot to answer support questions.
We'll be using React and Typescript in these examples, which you can adapt to fit your framework of choice.
Overview
This guide walks you through how to:
- Pull knowledge base content using our KMS API
- Process and prepare this content for your AI training pipeline
- Send responses through Discord using our Messaging API
While we won't dive deep into the AI/ML aspects of building a support bot, we'll provide guidance on where this fits in your development pipeline and point you to relevant resources for those components.
Before you begin
Make sure you have:
- A Unified.to account
- Access to a knowledge management system (e.g., Notion) containing your support content
- A Discord server where you want to deploy your bot
If you don't want to use real data from Notion or Discord, you can also take advantage of Unified.to's sandbox environment and work with synthetic data instead.
The following steps assume you already have your Unified.to account with your workspace ID and API key available. If you're new, check out our Quick Start guide to get familiar with the platform.
Set up your connections
The first thing you'll do is activate your preferred KMS platform to be the source of data for your bot. We'll use Notion in this guide, but our KMS API supports 8 platforms today and more are being added on a regular basis.
Note: If you're using the sandbox environment, for the steps below that ask you to enter your OAuth 2 credentials, just enter any random value e.g., test123
.
Activate Notion and Discord integrations
- On app.unified.to, go to the Integrations page
- Search for and select Notion
- Choose OAuth 2, enter your credentials (which can be anything if you're using the sandbox environment), and then click Activate
Repeat steps 1-3 but for Discord. If you want to use real data from Notion and Discord, check out our how-to guides on setting up with them:
(Sandbox environment) Create a test connection from the web app
If you're using the sandbox environment, then you can create test connections easily from the web app.
- On app.unified.to, go to the Connections page
- Click on Create test connection at the top right corner of the page (note: this will only show up if your current environment is the sandbox environment)
- Click on the Notion and Discord integrations
- Click Done
Skip ahead to the "Pull content from your knowledge base" step below.
(Production environments) Add the Authorization component to your app
To ask for your users' permission to access their Notion and Discord accounts, you can add Unified.to's Authorization component to your front-end:
- Add the following React component to your app. Make sure to replace
WORKSPACE_ID
with your actual workspace ID.
// TODO: Make sure to run 'npm install @unified-api/react-directory'
import UnifiedDirectory from '@unified-api/react-directory';
<UnifiedDirectory workspace_id={WORKSPACE_ID}/>
We also offer pre-built embedded components in Angular, Vue, and Svelte. See other ways to display the Authorization component in your app here.
The Authorization component displays a list of your integrations. When your user clicks on one of these integrations, the authorization flow will open up in a new tab. Your users will follow the steps to grant you access to their accounts. When that is done, they will be redirected back to your app (the default success URL location).
- Handle the callback after a user authorizes an integration. The connection ID will be sent back in the success URL - we need to save this to make API calls later on. In this example, we'll save the connection ID to local storage:
function handleAuthCallback() {
const urlParams = new URLSearchParams(window.location.search);
const connectionId = urlParams.get('id');
if (connectionId) {
console.log('New connection created:', connectionId);
// Store this connection ID securely - you'll need it for API calls
localStorage.setItem('unifiedConnectionId', connectionId);
}
}
window.addEventListener('load', handleAuthCallback);
For this guide, you can be your own test user:
- Click on the Notion integration in the Authorization component
- Grant access to your Notion workspace
- Repeat for Discord
- Save both connection IDs - you'll need them for the next steps
Note: In production, you should associate these connection IDs with your user's account in your database. For more information, see our guide on How to associate connection IDs with your users.
Pull content from your knowledge base
Now that you've got a connection ID, we can start making calls to the Unified API!
First, let's write some functions to recursively fetch all content from your KMS:
const axios = require('axios');
async function fetchContent(connectionId, apiKey) {
const spaces = await fetchAllSpaces(connectionId, apiKey);
const allContent = [];
for (const space of spaces) {
const pages = await fetchPagesInSpace(connectionId, apiKey, space.id);
allContent.push(...pages);
}
return allContent;
}
async function fetchAllSpaces(connectionId, apiKey) {
const response = await axios.get(
`https://api.unified.to/kms/${connectionId}/space`,
{
headers: {
Authorization: `Bearer ${apiKey}`,
},
params: {
limit: 100,
},
}
);
return response.data;
}
async function fetchPagesInSpace(connectionId, apiKey, spaceId) {
const response = await axios.get(
`https://api.unified.to/kms/${connectionId}/page`,
{
headers: {
Authorization: `Bearer ${apiKey}`,
},
params: {
limit: 100,
space_id: spaceId,
},
}
);
return response.data;
}
fetchContent(CONNECTION_ID, API_KEY)
Call fetchContent
in your app to see the results. You should see a collection of Pages.
Process the content for your AI pipeline
Here's an example of how to prepare your content for training an LLM:
function processContent(pages) {
return pages.map(page => ({
title: page.title,
content: page.download_url,
metadata: {
created_at: page.created_at,
updated_at: page.updated_at,
space_id: page.space_id
}
}));
}
processContent(pages)
At this point, you would typically:
- Clean and format the text
- Split content into appropriate chunk sizes for your LLM
- Generate embeddings
- Store in a vector database
For implementation details on these steps, we recommend checking out resources like:
- LangChain's documentation for vector stores
- Pinecone's tutorials for embedding and semantic search
- OpenAI's best practices for working with embeddings
Set up real-time message monitoring
To make your support bot responsive, you'll need to set up webhooks to receive notifications when users send messages in Discord channels.
- Create a webhook to monitor Discord messages:
async function createDiscordWebhook(connectionId, apiKey, hookUrl) {
try {
const response = await axios.post(
'https://api.unified.to/unified/webhook',
{
connection_id: connectionId,
hook_url: hookUrl,// Your server endpoint that will receive webhook events
object_type: 'messaging_message',
event: 'created'// Get notified when new messages are created
},
{
headers: {
Authorization: `Bearer ${apiKey}`
}
}
);
return response.data;
} catch (error) {
console.error('Error creating webhook:', error);
throw error;
}
}
createDiscordWebhook(CONNECTION_ID, API_KEY, 'https://localhost:8000/webhook')
- Set up an endpoint on your server to handle incoming webhook events. For example:
app.post('/webhook', async (req, res) => {
// Verify webhook signature
const { data, webhook, nonce, sig } = req.body;
// Handle new messages
for (const message of data) {
// Ignore messages from the bot itself
if (message.author_member.id === BOT_USER_ID) continue;
// Process the message and generate a response
const response = await generateBotResponse(message.message);
// Send the response back to the channel
await sendDiscordResponse(
DISCORD_CONNECTION_ID,
API_KEY,
message.channel_id,
response
);
}
res.sendStatus(200);
});
- Validate incoming webhooks (recommended):
const crypto = require('crypto');
function verifyWebhookSignature(data, nonce, signature, workspaceSecret) {
const hmac = crypto.createHmac('sha1', workspaceSecret);
const calculatedSignature = hmac
.update(JSON.stringify(data) + nonce)
.digest('base64');
return calculatedSignature === signature;
}
With this setup, your bot can:
- Receive notifications whenever a new message is sent in the monitored channels
- Process those messages through your AI pipeline
- Send responses back to the appropriate channel
For more details on webhooks at Unified.to, see our guide to webhooks.
Send responses through Discord
When you're ready to respond to a user's question, use our Messaging API to send the message back to Discord. Here's an example implementation with error handling and message formatting:
async function sendDiscordResponse(connectionId, apiKey, channelId, response) {
try {
// Format the response for better readability
const formattedResponse = formatDiscordMessage(response);
await axios.post(
`https://api.unified.to/messaging/${connectionId}/message`,
{
channel_id: channelId,
message: formattedResponse,
message_html: formattedResponse,// Optional: Use for formatted text
},
{
headers: {
Authorization: `Bearer ${apiKey}`,
},
}
);
} catch (error) {
console.error('Error sending message:', error);
// Handle common errors
throw error;
}
}
function formatDiscordMessage(response) {
// Example formatting for Discord markdown
return `**Bot Response:**\n${response}`;
}
// Example usage with different message types
async function sendBotResponses(connectionId, apiKey, channelId) {
// Simple text response
await sendDiscordResponse(
connectionId,
apiKey,
channelId,
'Here is the answer to your question...'
);
// Response with code block
await sendDiscordResponse(
connectionId,
apiKey,
channelId,
'```javascript\nconst example = "code block";\n```'
);
// Response with rich formatting
await sendDiscordResponse(
connectionId,
apiKey,
channelId,
'**Bold text** and *italic text* with a [link](https://example.com)'
);
}
Putting it all together
Here's a basic example of how these components work together:
async function initializeSupportBot() {
// Initialize your environment
// We have not shown the `getKMSConnection` and `getDiscordConnection` functions in this guide
// Implement them in your favourite framework to get the connection IDs from your database
const KMS_CONNECTION_ID = await getKMSConnection(user_id);
const DISCORD_CONNECTION_ID = await getDiscordConnection(user_id);
const API_KEY = process.env.API_KEY;
// Fetch and process content
const kmsContent = await fetchKmsContent(KMS_CONNECTION_ID, API_KEY);
const processedContent = processContentForAI(kmsContent);
// Here you would:
// 1. Train or update your AI model
// 2. Set up your Discord bot event listeners
// 3. Initialize your vector database
// Example of handling a question
async function handleQuestion(channelId, question) {
// Your AI processing logic here
const response = 'Your AI-generated response';
await sendDiscordResponse(
DISCORD_CONNECTION_ID,
API_KEY,
channelId,
response
);
}
}
Conclusion
Congratulations! You've built the starting foundations for an AI-powered support bot using Unified.to's APIs. In this guide, you saw how to:
- Connect to knowledge management systems like Notion to access your content
- Set up real-time message monitoring in Discord
- Send formatted responses back to users
The best part? This is just the beginning. Since you're using Unified.to's APIs, you can easily expand your bot to work with other platforms. Want to pull content from Coda instead of Notion? Or maybe deploy your bot on Slack? It's just a matter of changing the connection - your core logic stays the same.
Happy building!