How to Build a ModVC Bot
Most modern chat platforms force you into bad architectural patterns. They make you maintain a persistent WebSocket connection, track heartbeat intervals, and run expensive EC2 instances 24/7 just so your bot can listen for the word "ping". ModVC does not do that. You can build a fully featured bot using entirely stateless HTTP functions.
The Mental Model
Your bot operates on two isolated paths.
- 1The Read Path (Webhooks)When a user clicks a button or runs a command in a channel, ModVC sends a standard HTTP POST request to a URL you provide. You parse the JSON, figure out what they want, and return a 200 OK. That is it. No websockets. No polling.
- 2The Write Path (REST API)When your bot needs to actively do something, like kicking a spammer or creating a new support ticket channel, you send a standard HTTP POST request to
https://modvc.org/api/botwith your Bot Token.
Step 1: Provisioning the Bot Identity
Bots don't spawn out of nowhere. You have to register an Application in the Developer Portal first.
- Go to the Developer Portal.
- Create a new Application. Name it whatever you want your bot to be called.
- Navigate to the Bot tab and click Add Bot.
- Copy your Bot Token. Treat this string exactly like a root database password. If you commit this to a public GitHub repository, someone will hijack your bot and destroy every server it has administrative access to.
A note on server invites
Just because a bot exists doesn't mean it can read messages. A server administrator must explicitly invite your bot using an OAuth2 URL generated in the portal. During that invite flow, the administrator grants specific permission roles (like MANAGE_CHANNELS). If your bot tries to create a channel but wasn't granted that role, the API will reject your request with a 403 Forbidden error.
Step 2: The Webhook Server (Read Path)
We need a public URL that ModVC can send POST requests to. If you are developing locally on your laptop, ModVC cannot reach localhost:3000. You must use a tool like Ngrok or Cloudflare Tunnels to expose your local port to the public internet.
Here is the bare minimum Express server required to handle ModVC events. Save this as server.js.
const express = require('express');
const app = express();
app.use(express.json());
// This is the endpoint you paste into the Developer Portal
app.post('/webhook', (req, res) => {
const payload = req.body;
// ModVC sends a PING event the moment you paste your URL in the portal.
// You must return a 200 OK, otherwise the portal will reject your URL.
if (payload.type === 'PING') {
console.log("Received validation ping from ModVC.");
return res.status(200).json({ success: true });
}
// A user clicked an interactive button attached to one of your messages.
if (payload.type === 'MESSAGE_COMPONENT') {
const buttonId = payload.custom_id;
const clickerId = payload.user_id;
const serverId = payload.guild_id;
console.log(`User ${clickerId} clicked ${buttonId} in server ${serverId}`);
// You MUST return a 200 OK immediately.
// If you don't respond within 3 seconds, the user sees an "Interaction Failed" error.
return res.status(200).json({
success: true,
message: "Button click received!"
});
}
res.status(400).send("Unknown event type");
});
app.listen(3000, () => {
console.log("Webhook server listening on port 3000");
});Step 3: Executing Actions (Write Path)
Your bot needs to talk back. Let's say someone clicked a "Create Ticket" button, and your webhook received the event. Your server now needs to tell ModVC to actually create a private text channel.
You do this by sending a POST request to /api/bot. We use the native Node fetch API below, meaning you don't need any external dependencies.
const BOT_TOKEN = process.env.BOT_TOKEN;
const API_BASE = "https://modvc.org/api/bot";
async function createTicketChannel(serverId, userId) {
const res = await fetch(API_BASE, {
method: "POST",
headers: {
"Authorization": `Bot ${BOT_TOKEN}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
action: "create_channel",
server_id: serverId,
name: `ticket-${userId.substring(0,4)}`,
type: "text",
isPrivate: true,
permissionOverrides: {
// Deny everyone else, but grant the specific user access
"@everyone": { view_channel: false },
[userId]: { view_channel: true, send_messages: true }
}
})
});
if (!res.ok) {
const errorText = await res.text();
console.error("Failed to create channel. Check permissions.", errorText);
return null;
}
const data = await res.json();
console.log(`Successfully created channel ${data.channel.id}`);
return data.channel;
}
// Example usage:
// createTicketChannel("server_abc", "user_123");Step 4: Maintaining an Online Presence
By default, bots appear offline. This confuses users. To make your bot show up in the member list with a green online dot, you have to ping the Edge Presence API.
ModVC's presence system is ruthless. If it does not hear from your bot for 45 seconds, it marks you as offline. Set up a simple interval loop that sends a presence ping every 30 seconds as long as your server is running.
setInterval(async () => {
try {
await fetch("https://modvc.org/api/bot", {
method: "POST",
headers: {
"Authorization": `Bot ${process.env.BOT_TOKEN}`,
"Content-Type": "application/json"
},
body: JSON.stringify({ action: "presence" })
});
console.log("Sent presence heartbeat.");
} catch (e) {
console.warn("Heartbeat failed. Retrying in 30 seconds.");
}
}, 30_000);Where to go from here: Edge Deployment
The Node.js examples above are perfect for local testing or running on a VPS. But because the entire architecture relies on HTTP POSTs instead of open WebSockets, you don't actually need a Node.js server at all in production.
The absolute best way to run a ModVC bot is to deploy your webhook handler to a Cloudflare Worker or Vercel Edge Function. It costs nothing, scales instantly to millions of requests, and eliminates the need to manage server infrastructure or worry about process restarts.