Skip to content

Connection and identity ​

Use a manual connection for development and Marketplace mode for URL-based registrations. The identity must survive updates independently of the chosen mode.

Reference: BotOptions, ServeOptions and methods and events.

Marketplace mode (multi-server) ​

To distribute a bot through a manifest, use serve(). In the tutorial project, change monkyBot.modes to ["manual", "marketplace"], use the entry point below, build and run npm run cli -- setup again in that mode. The CLI supplies the identity, registration path, configured port and host. Choose URL installation in the wizard; the entry point also preserves manual mode.

ts
import {
  BotClient, validateBotPublicHost, validateBotServePort,
  validateBotServerUrl, validateBotToken,
} from '@monky/bot-sdk';

function requiredEnvironment(name: string): string {
  const value = process.env[name];
  if (!value) throw new Error(`Missing environment variable: ${name}`);
  return value;
}

const marketplace = process.env.MONKY_SERVE === 'true';
const bot = new BotClient({
  requestedCapabilities: ['commands'],
  publicKey: requiredEnvironment('MONKY_BOT_PUBLIC_KEY'),
  registrationFile: marketplace ? requiredEnvironment('MONKY_BOT_REGISTRATION_FILE') : undefined,
  name: process.env.MONKY_BOT_NAME ?? 'My Bot',
});

bot.command({
  name: 'ping',
  description: 'Check whether the bot is online',
  localizations: { 'pt-BR': { description: 'Verifica se o bot está respondendo' } },
  handler: (ctx) => ctx.reply(ctx.locale === 'en' ? 'Pong! I am online.' : 'Pong! Estou online.'),
});
bot.on('error', (error: Error) => console.error('[bot]', error.message));
const shutdown = () => {
  void bot.close().catch((error: unknown) => {
    console.error('[shutdown]', error);
    process.exitCode = 1;
  });
};
process.once('SIGINT', shutdown);
process.once('SIGTERM', shutdown);

if (marketplace) {
  await bot.serve({
    name: process.env.MONKY_BOT_NAME ?? 'My Bot',
    description: 'Example bot / Bot de exemplo',
    port: validateBotServePort(requiredEnvironment('MONKY_SERVE_PORT')),
    publicHost: validateBotPublicHost(requiredEnvironment('MONKY_SERVE_PUBLIC_HOST')),
    // Only set when the profile has a public URL (TLS proxy).
    publicUrl: process.env.MONKY_SERVE_PUBLIC_URL || undefined,
  });
} else {
  bot.connect({
    serverUrl: validateBotServerUrl(requiredEnvironment('MONKY_SERVER_URL')),
    token: validateBotToken(requiredEnvironment('MONKY_BOT_TOKEN')),
  });
}

After setup, run npm run cli -- start --foreground. The process.env.MONKY_SERVE === 'true' comparison is explicit: the string 'false' is also truthy in JavaScript. Only declare implemented modes.

This exposes:

  • GET /manifest — returns the bot manifest (name, description, requested capabilities, registration URL)
  • POST /register — receives the token from each server that adds the bot

Only Monky servers talk to these routes, never web pages, so they do not answer CORS. POST /register only accepts Content-Type: application/json, with a body of up to 16 KiB. Each source address may make 10 registration attempts per minute, and at most 4 registrations complete at the same time; beyond that, the bot answers 429. Behind a proxy, the counted address is the proxy's.

Each server that adds the bot creates an independent WebSocket connection. The bot manages all of them automatically, with reconnection.

Persistent registrations ​

Set registrationFile to recover connections after restarting the process. Without it, registrations only exist during the current run. When registration includes serverUrl, the SDK confirms POST /register after authenticating to the server and saving the registration; rejected credentials cannot replace a valid registration. The server rolls back account creation if the callback rejects registration or fails to confirm a valid key. A known serverId only accepts the same token and URL; a public callback cannot redirect that registration to another destination.

The file contains tokens and must remain outside source control with restricted access. Writes are atomic and use 0600 permissions on POSIX systems. Back it up together with the Ed25519 identity; do not generate new keys when updating. The limits are 1,000 registrations and 4 MiB per file. Do not share the same file between simultaneous bot processes. Invalid files stop startup without being overwritten. disconnect() and close() do not erase saved registrations.

Registrations lost by versions that only kept them in memory need authorization again: the server cannot recover a token from its hash. After updating MonkyBot, revoke the old entry and add it by URL once, reapplying any account-specific settings. Do not delete the identity keys.

Authentication errors are emitted through error as well as auth_failed. A protocol mismatch permits further attempts with autoReconnect; invalid tokens do not enter a retry loop. A rejected photo update does not disconnect the bot or prevent command registration.

Network requirements ​

For Monky servers to reach the manifest and register the bot, the configured port (default 7780) must be externally accessible:

bash
# Run from the Monky server's machine, not just from the bot's machine
curl --fail --max-time 10 http://YOUR-IP:7780/manifest

# If using iptables (Linux):
sudo iptables -I INPUT 1 -p tcp --dport 7780 -j ACCEPT

# If using ufw (Ubuntu):
sudo ufw allow 7780/tcp

# If on cloud (AWS, GCP, Azure, etc.):
# Allow port 7780 TCP in your Security Group / Firewall Rules

Also, publicHost must be the machine's public IP or domain — localhost only works if bot and server run on the same machine.

HTTPS with a reverse proxy ​

Without publicUrl, the manifest announces http://<publicHost>:<port>/register, and the Monky server sends the bot token to that address unencrypted. To keep the token protected on the way, put the bot behind a TLS reverse proxy (Caddy, nginx, etc.) and set the public origin in publicUrl:

ts
await bot.serve({
  name: 'My Bot',
  port: 7780,
  host: '127.0.0.1', // only the proxy, on the same machine, reaches the port
  publicUrl: 'https://bot.example.com',
});

The manifest then announces https://bot.example.com/register, and the Monky server installs the bot from https://bot.example.com/manifest. In the manifest, publicUrl takes precedence over publicHost and port. Give only the origin, with no path, user name, password, query or fragment: the proxy forwards the domain root to the bot, and that is where doctor and the server prove access. When installing, use the final manifest URL: the server does not follow redirects, such as the http:// to https:// one proxies usually send.

With the bot CLI, save the URL in the profile instead of hard-coding it: npm run cli -- config set publicUrl https://bot.example.com, or --public-url https://bot.example.com in setup --non-interactive. The runner hands the value over in MONKY_SERVE_PUBLIC_URL, which the entry in the example above passes to serve(), and start and doctor then check the manifest through that URL. config set publicUrl clear goes back to announcing the host and port.

Connectivity must also work from the bot to the Monky server. Registration sends the WebSocket URL the client used to reach the server, preserving its host, port and path; it does not turn a 0.0.0.0 listener into localhost. If the bot runs on a VPS and the server on your computer, connect to Monky using an IP or domain reachable from that VPS. Firewall rules, port forwarding or a proxy must allow the return connection; reaching the bot manifest does not prove that this second path works.

A server advertised through loopback only accepts installation when both the manifest and registration URLs also use loopback. Otherwise, the preview is rejected before creating an account or sending a token, with guidance about the required address. For a bot and server on the same machine, use local URLs on both sides, or connect to the server through its external address. Proxies must preserve the original Host and set X-Forwarded-Proto: https when terminating TLS; the callback then uses wss. This adjustment does not require deleting identities or valid registrations.

The request must return the manifest JSON. If monkybot status reports errored, fix the error in monkybot logs first: opening ports cannot start a failing process. iptables rules must precede blocking rules and be persisted according to your distribution; behind NAT, also configure port forwarding.

Useful properties ​

ts
bot.serverCount;  // Number of connected servers
bot.serverIds;    // List of server IDs
bot.registeredServerCount; // Known authenticated registrations, including offline

Events ​

ts
bot.on('connected', ({ serverId }) => console.log(`Connected to ${serverId}`));
bot.on('disconnected', ({ serverId }) => console.log(`Disconnected from ${serverId}`));
bot.on('registered', ({ serverId, serverName }) => console.log(`Registered on ${serverName}`));
bot.on('error', (err) => console.error(err));
bot.on('serving', ({ port, manifest }) => console.log(`Manifest at :${port}/manifest`));

Security: token and identity ​

The token is the bot's secret: whoever holds the token of a registration connects as the bot on that server. Treat it like a password. Keep the registrationFile with restricted access, never log the token (the registered event does not include it) and, in marketplace mode, use HTTPS through publicUrl. To reach the server, prefer wss://.

The Ed25519 public key identifies the bot. On the first connection, the server binds the presented key to the bot (TOFU, Trust On First Use) and from then on rejects connections with a different key. In marketplace mode, the binding happens during installation.

The public key is no secret (it travels in the manifest and in commands), so what protects a leaked token is proof of possession of the private key. When the bot holds its private key, the SDK offers the bot-key-proof feature: the server sends a challenge and the bot signs it with the private key, bound to the token of that connection. After the first valid proof, the server requires it from that bot on every connection. From then on, whoever holds only the token and the public key is refused (FEATURE_REQUIRES_UPDATE) without disconnecting the real bot. Running through the bot CLI, this is already on; in code, pass the key in privateKey:

ts
const bot = new BotClient({
  requestedCapabilities: [],
  publicKey: 'YOUR_ED25519_PUBLIC_KEY_HEX',
  privateKey: readFileSync('.keys/private.pem', 'utf8'), // PEM or KeyObject
});

Older bots and servers, without the feature, keep connecting with the token alone. A bot that never proved its key is therefore still protected only by its token. Every refused attempt (invalid token, wrong signature or missing proof) spends the address's connection attempt quota, like a wrong password.

If a token leaks, revoke the bot on the server and add it again. The new registration gets another token, and the bot keeps the same identity.

💡 The Monky Bot automatically generates and reuses its Ed25519 key pair. When building your own bot with the SDK, generate and persist your key using Node.js cryptography APIs and pass the public key in publicKey and the private key in privateKey; do not generate a new identity on every restart.

Bot photos ​

The bot process owns its name and photo, not client-side management. Its operator can configure the CLI's botName; bot code publishes the identity through the SDK:

ts
import { readFileSync } from 'node:fs';
import { BotClient } from '@monky/bot-sdk';

const bot = new BotClient({
  requestedCapabilities: [],
  publicKey: 'YOUR_ED25519_PUBLIC_KEY_HEX',
  name: 'My Bot',
  avatarBase64: `data:image/png;base64,${readFileSync('bot.png').toString('base64')}`,
});

In marketplace mode, serve({ name, icon, ... }) accepts the same image in icon. Supply base64 or a data URI, not an image URL. The server validates format/size and hosts the photo. avatarBase64: null explicitly removes a previous photo; omitting the field preserves it. The SDK announces its name during initial authentication and reapplies its profile on connection, including existing links. Photo failures are reported without hiding the bot's commands. Administrators cannot override identity through the client or profile API. The official MonkyBot includes the Monky logo in its package.