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

PropertyValue
TransportSocket.io (WebSocket + HTTP long-poll fallback)
Namespace/bot-gateway
Auth methodBot token via Socket.io handshake auth
Connections per bot1 (connecting again disconnects the previous session)
Gateway versionv: 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.

⚠️
Connecting a second time with the same token will disconnect the existing session. Only one gateway connection per bot is allowed.

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
});
ℹ️
Socket.io handles its own ping/pong internally. The 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

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

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

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

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

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

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

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

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

CHANNEL_UPDATE

Fired when a channel is updated. Same payload shape as CHANNEL_CREATE.

CHANNEL_DELETE

CHANNEL_DELETE

Fired when a channel is deleted.

{
  "id":       "snowflake",
  "guild_id": "snowflake"
}

ROLE_UPDATE

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

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
TypeNameTrigger
1APPLICATION_COMMANDUser invokes a slash command
3MESSAGE_COMPONENTUser clicks a button on a bot message

GUILD_MEMBER_UPDATE

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.

ℹ️
Subscription management is done via the REST API — see Event Subscriptions in the Bot API docs. Deliveries time out after 5 seconds and are fire-and-forget (no retry).

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
      }),
    }
  );
});
ℹ️
Ephemeral responses are delivered via the real-time gateway to the invoking user only. They are not stored in the database and cannot be retrieved via the REST API. Followup messages sent with 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);
});