Bot Gateway
The NexusGuild Bot Gateway delivers real-time events to your bot over a persistent Socket.io connection. Your bot connects once and receives events for every guild it's installed in, no polling required.
Overview
| Property | Value |
|---|---|
| Transport | Socket.io (WebSocket + HTTP long-poll fallback) |
| Namespace | /bot-gateway |
| Auth method | Bot token via Socket.io handshake auth |
| Connections per bot | 1 (connecting again disconnects the previous session) |
| Gateway version | v: 1 (returned in READY) |
Connecting
Use the Socket.io client library to connect to the /bot-gateway namespace:
import { io } from 'socket.io-client';
const gateway = io('https://app.nexusguild.gg/bot-gateway', {
auth: {
token: 'YOUR_BOT_TOKEN',
},
transports: ['websocket'], // prefer WebSocket
reconnection: true,
reconnectionDelay: 5000,
});
gateway.on('connect', () => {
console.log('Connected to NexusGuild gateway');
});
gateway.on('connect_error', (err) => {
console.error('Gateway connection failed:', err.message);
});
Authentication
Pass your bot token in the Socket.io auth object at connection time.
If the token is missing or invalid, the connection is rejected with an error message.
Heartbeat
Send a HEARTBEAT event periodically to verify connectivity.
The server responds with HEARTBEAT_ACK.
// Send heartbeat every 30 seconds
setInterval(() => {
gateway.emit('HEARTBEAT');
}, 30000);
gateway.on('HEARTBEAT_ACK', () => {
// Connection is alive
});
HEARTBEAT event
is an optional application-level health check, not a requirement for staying connected.Gateway Events
After connecting and authenticating, the gateway delivers these events to your bot.
READY
Sent immediately after a successful connection. Contains bot identity and gateway version.
{
"bot": {
"id": "snowflake",
"name": "MyBot"
},
"v": 1
}
Following READY, one GUILD_CREATE is emitted per guild the bot is installed in.
GUILD_CREATE
Sent once per guild on connection, providing a complete snapshot of each guild's state.
{
"id": "snowflake",
"name": "My Server",
"icon": null,
"owner_id": "snowflake",
"approximate_member_count": 42,
"channels": [
{
"id": "snowflake",
"type": 0,
"name": "general",
"topic": null,
"position": 0,
"parent_id": null
}
],
"roles": [
{
"id": "snowflake",
"name": "@everyone",
"color": 0,
"permissions": "103926848",
"position": 0,
"mentionable": false
}
],
"members": [
{
"user": { "id": "snowflake", "username": "Alice", "bot": false },
"nick": null,
"roles": [],
"joined_at": "2026-01-01T00:00:00.000Z"
}
]
}
MESSAGE_CREATE
Fired when any message is sent in a guild the bot is in. Payload mirrors the REST message object.
{
"id": "snowflake",
"channel_id": "snowflake",
"user_id": "snowflake",
"username": "Alice",
"avatar": null,
"content": "Hello!",
"created_at": "2026-03-07T12:00:00.000Z",
"attachments": null,
"reply_to_content": null,
"thread_channel_id": null
}
MESSAGE_UPDATE
Fired when a message is edited. Payload includes both the pre-edit and post-edit message snapshots.
{
"id": "snowflake",
"channel_id": "snowflake",
"guild_id": "snowflake",
"before": {
"content": "Old message",
"attachments": null,
"edited_at": null
},
"after": {
"content": "Edited message",
"attachments": null,
"edited_at": "2026-03-07T12:05:00.000Z"
},
"edited_by": {
"id": "snowflake",
"username": "Alice"
}
}
MESSAGE_DELETE
Payload now includes the original message snapshot, the original author, and the actor who deleted it.
Fired when a message is deleted (including bulk deletes — one event per deleted message).
{
"id": "snowflake", // message ID
"channel_id": "snowflake",
"guild_id": "snowflake",
"message": {
"content": "Original message text",
"attachments": null
},
"author": {
"id": "snowflake",
"username": "Alice"
},
"deleted_by": {
"id": "snowflake",
"username": "ModJane"
},
"deleted_by_author": false
}
MEMBER_JOIN
Fired when a user joins a guild.
{
"user": {
"id": "snowflake",
"username": "Bob",
"avatar": null,
"bot": false
},
"guild_id": "snowflake",
"joined_at": "2026-03-07T12:00:00.000Z"
}
MEMBER_LEAVE
Fired when a user leaves or is removed from a guild.
{
"user": {
"id": "snowflake",
"username": "Bob",
"avatar": null,
"bot": false
},
"guild_id": "snowflake"
}
CHANNEL_CREATE
Fired when a channel is created in a guild. Payload is a channel object.
{
"id": "snowflake",
"type": 0,
"guild_id": "snowflake",
"name": "announcements",
"topic": null,
"position": 1,
"parent_id": null
}
CHANNEL_UPDATE
Fired when a channel is updated. Same payload shape as CHANNEL_CREATE.
CHANNEL_DELETE
Fired when a channel is deleted.
{
"id": "snowflake",
"guild_id": "snowflake"
}
ROLE_UPDATE
Fired when a role is created, edited, or deleted. Check for a deleted: true
field to distinguish deletion from creation/update.
// Created or updated:
{
"id": "snowflake",
"name": "Mod",
"color": 3447003,
"permissions": "8",
"position": 1,
"mentionable": false,
"hoist": false
}
// Deleted:
{
"deleted": true,
"id": "snowflake",
"guild_id": "snowflake"
}
INTERACTION_CREATE
Fired when a user invokes one of your bot's slash commands (type: 1)
or clicks a button on one of your bot's messages (type: 3). Respond using the
interaction callback endpoint with the
id and token from this event.
You have 3 seconds to respond (or send a deferred response first).
Slash Command (type 1)
{
"id": "snowflake", // interactionId
"token": "interaction_token", // use in callback URL
"type": 1, // slash command
"guild_id": "snowflake",
"channel_id": "snowflake",
"member": {
"id": "snowflake",
"username": "Alice",
"avatar": null,
"roles": []
},
"data": {
"id": "snowflake", // command ID
"name": "ping", // command name
"options": [] // parsed arguments — see Command Options
}
}
Button Click (type 3)
{
"id": "snowflake",
"token": "interaction_token",
"type": 3, // MESSAGE_COMPONENT
"guild_id": "snowflake",
"channel_id": "snowflake",
"message_id": "snowflake",
"member": {
"id": "snowflake",
"username": "Alice",
"avatar": null,
"roles": []
},
"data": {
"custom_id": "my_button_1", // the custom_id set when creating the button
"component_type": 2 // button
}
}
Interaction Types
| Type | Name | Trigger |
|---|---|---|
1 | APPLICATION_COMMAND | User invokes a slash command |
3 | MESSAGE_COMPONENT | User clicks a button on a bot message |
GUILD_MEMBER_UPDATE
Fired when a member's roles change via the role management endpoints or when their nickname changes.
{
"guild_id": "snowflake",
"user": {
"id": "snowflake",
"username": "Alice",
"avatar": null,
"bot": false
},
"nick": "AliceTheGreat",
"nick_change": {
"old": "Ali",
"new": "AliceTheGreat"
},
"roles": ["roleSnowflakeA", "roleSnowflakeB"],
"added_roles": ["roleSnowflakeB"],
"removed_roles": [],
"joined_at": "2026-01-01T00:00:00.000Z"
}
nick_change is included when the nickname was edited. added_roles and removed_roles are included for role changes, and are empty arrays for nickname-only updates.
Event Subscriptions (Webhook Delivery)
If your bot is offline (not connected to the gateway), you can still receive events by registering a webhook subscription. NexusGuild will POST each matching event to your URL with an HMAC-SHA256 signature so you can verify authenticity.
If your bot is connected to the gateway when an event fires, webhook delivery is skipped — the gateway takes priority.
Example: Basic Bot
A minimal bot that connects, logs guild names, and echoes messages:
import { io } from 'socket.io-client';
const BOT_TOKEN = 'your_token_here';
const BASE_URL = 'https://app.nexusguild.gg';
const guilds = new Map();
const gateway = io(`${BASE_URL}/bot-gateway`, {
auth: { token: BOT_TOKEN },
transports: ['websocket'],
});
gateway.on('READY', ({ bot }) => {
console.log(`Logged in as ${bot.name} (${bot.id})`);
});
gateway.on('GUILD_CREATE', (guild) => {
guilds.set(guild.id, guild);
console.log(`In guild: ${guild.name} (${guild.members.length} members)`);
});
gateway.on('MESSAGE_CREATE', async (msg) => {
if (msg.content === '!ping') {
await fetch(`${BASE_URL}/api/v1/channels/${msg.channel_id}/messages`, {
method: 'POST',
headers: {
'Authorization': `Bot ${BOT_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ content: 'Pong!' }),
});
}
});
Example: Slash Command Bot
Registering a command and responding to it:
// 1. Register the command (one-time setup, session auth required)
await fetch(`/api/bots/${botId}/servers/${guildId}/commands`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
// session cookie sent automatically
body: JSON.stringify({
name: 'roll',
description: 'Roll a dice',
options: [
{
name: 'sides',
description: 'Number of sides',
type: 4, // INTEGER
required: false,
}
]
}),
});
// 2. Handle the INTERACTION_CREATE event
gateway.on('INTERACTION_CREATE', async (interaction) => {
if (interaction.data.name !== 'roll') return;
const sides = interaction.data.options?.[0]?.value ?? 6;
const result = Math.floor(Math.random() * sides) + 1;
await fetch(
`/api/interactions/${interaction.id}/${interaction.token}/callback`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
type: 4,
content: `🎲 You rolled a **${result}** (d${sides})`,
}),
}
);
});
Example: Ephemeral Response
Ephemeral responses are only visible to the user who invoked the command — they are never
saved to the channel and disappear when the user navigates away or dismisses them.
Set flags: 64 in the callback body to make a response ephemeral.
gateway.on('INTERACTION_CREATE', async (interaction) => {
if (interaction.data.name !== 'secret') return;
await fetch(
`/api/interactions/${interaction.id}/${interaction.token}/callback`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
type: 4,
content: '🤫 Only you can see this!',
flags: 64, // EPHEMERAL — not saved, not visible to others
}),
}
);
});
flags: 64 are also ephemeral.
See the Message Flags reference for all flag values.
Example: Button Components
Sending a message with buttons, then handling a click:
// 1. Send a message with a button
await fetch(`${BASE_URL}/api/v1/channels/${channelId}/messages`, {
method: 'POST',
headers: {
'Authorization': `Bot ${BOT_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
content: 'Pick an option:',
components: [
{
type: 1, // ACTION_ROW
components: [
{ type: 2, style: 1, label: 'Yes', custom_id: 'confirm_yes' },
{ type: 2, style: 4, label: 'No', custom_id: 'confirm_no' },
],
},
],
}),
});
// 2. Handle the button click (INTERACTION_CREATE type 3)
gateway.on('INTERACTION_CREATE', async (interaction) => {
if (interaction.type !== 3) return;
const reply = interaction.data.custom_id === 'confirm_yes'
? 'You chose Yes!'
: 'You chose No.';
await fetch(
`${BASE_URL}/api/interactions/${interaction.id}/${interaction.token}/callback`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ type: 4, content: reply, flags: 64 }),
}
);
});
Example: Webhook Delivery
Registering a subscription and verifying incoming webhook payloads:
// 1. Register a subscription (one-time setup, session auth)
await fetch(`/api/bots/${botId}/subscriptions`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
webhook_url: 'https://mybot.example.com/nexus-events',
signing_key: 'my-secret-key',
events: ['MESSAGE_CREATE', 'MESSAGE_UPDATE'],
}),
});
// 2. Receive and verify on your server (Node.js/Express)
import crypto from 'crypto';
app.post('/nexus-events', express.raw({ type: 'application/json' }), (req, res) => {
const sig = req.headers['x-nexusguild-signature'];
const expected = crypto
.createHmac('sha256', 'my-secret-key')
.update(req.body)
.digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
return res.status(401).end();
}
const { event, data } = JSON.parse(req.body);
console.log(`Received ${event}`, data);
res.sendStatus(200);
});