umbot
    Preparing search index...

    Configuration and security

    This page is machine-translated from the Russian original. If something reads oddly, the Russian version is the source of truth — open an issue.

    To store tokens and other sensitive data securely, you can use two approaches:

    bot.setAppConfig({ env: './.env' });
    

    An example .env file:

    # Platform tokens
    TELEGRAM_TOKEN=123456:ABC-DEF...
    VK_TOKEN=vk1.a.abc123...
    VK_CONFIRMATION_TOKEN=abcdef       # required for VK (to confirm the webhook)
    VK_SECRET_KEY=abc123...              # optional: the VK secret key for verifying requests
    VIBER_TOKEN=1234567890-ABCDEF...
    ALISA_TOKEN=y0_AgAAAAA...          # the skill's OAuth token (for media uploads), without the "OAuth " prefix
    MARUSIA_TOKEN=abc.123...
    MAX_TOKEN=abc123...
    SMARTAPP_TOKEN=...                  # Sber SmartApp token (generated by the CLI; not required for the adapter to work)
    
    # Webhook secrets (see "Webhook signature verification" below). They go to
    # tokens.telegram.webhookSecret / tokens.max_app.webhookSecret and enable signature verification.
    # They are created and registered by `npx umbot webhook <telegram|max> <https-url>`.
    # Do not leave placeholders here: with any non-empty value the bot rejects requests with a different secret.
    TELEGRAM_WEBHOOK_SECRET=...
    MAX_WEBHOOK_SECRET=...
    
    # Yandex SpeechKit — for TTS in chatbots (Telegram/VK/Max).
    # The value is automatically written to speech_kit_token of all three platforms.
    # A service account API key is recommended (sent as `Api-Key`); an IAM token `t1.…`
    # is also accepted (sent as `Bearer`), but it lives no longer than 12 hours.
    SPEECH_KIT_TOKEN=AQVN...
    
    # MongoDB connection (if you use MongoAdapter)
    DB_HOST=mongodb://localhost:27017
    DB_USER=root
    DB_PASSWORD=secret
    DB_NAME=umbot
    

    Important! Never add .env files to a Git repository. Use different tokens for development and production. Set ALISA_TOKEN without the OAuth prefix — the framework adds it itself. The deprecated YANDEX_TOKEN name is supported for backward compatibility, but ALISA_TOKEN takes precedence.

    What happens if the file is not found? The framework tries to get the tokens from process.env. If they are not there either, an error message is written to the log file. The adapter stays registered, but operations that need a token (sending messages, uploading media) will not work.

    Environment variables without env. If env is not configured at all, the framework still silently tries to read the known variables (TELEGRAM_TOKEN, VK_TOKEN, ...) from process.env and fill in the tokens with them — this lets you pass tokens via docker run -e or a serverless function environment without env: 'local'. Tokens that are already set are not overwritten.

    It is convenient to keep the keys of your own integrations (a weather API, a CRM) in the same .env. The framework reads only its own variables from it, and you can read yours with the same parser — the loadEnvFile function from umbot/utils: the same rules for comments (" #" outside quotes), quotes and empty values as the framework uses.

    import { loadEnvFile } from 'umbot/utils';

    const envFile = loadEnvFile('./.env').data ?? {};
    const weatherKey = process.env['WEATHER_KEY'] || envFile['WEATHER_KEY'] || '';

    Projects generated by umbot create from-flow get an env('NAME') helper for this in src/utils.ts (see json-format, "HTTP requests: variables and secrets").

    bot.setAppConfig({
    db: {
    host: 'mongodb://localhost:27017',
    user: 'bot_user',
    pass: 'secure_password',
    database: 'bot_database',
    },
    tokens: {
    telegram: {
    token: 'your-telegram-token',
    },
    vk: {
    token: 'your-vk-token',
    },
    },
    });

    The mechanics are as follows: the adapter constructor token (new TelegramAdapter('token')) is written to the config when bot.use() is called (in the adapter's init()). After that, env decides:

    1. An explicitly configured env (a file or 'local') — setAppConfig({ env }) and every subsequent setPlatformParams overwrite the tokens from env: values from .env/process.env overwrite both the constructor token and the inline tokens (no matter whether setAppConfig is called before or after bot.use()).
    2. The adapter constructor argument — wins if env is not configured at all: then setPlatformParams only fills in missing tokens from process.env without overwriting the ones that are set.
    3. The inline tokens object in setAppConfig — is merged with the platform's existing tokens.
    4. process.env without a configured env — only fills in missing tokens, overwriting nothing.

    A practical tip: do not mix approaches for one platform. Either pass the token in the adapter constructor and do not configure env, or use .env/process.env and create adapters without a token.

    Scenario Recommendation
    Development, prototype env: '.env' — simple and safe
    Production on a server env: 'local' + environment variables on the server
    Tests Pass directly in config.tokens or in the adapter constructor
    Several environments (dev/prod) .env files with different tokens, passed via env

    setAppConfig takes a Partial<IAppConfig> object:

    Field Type Description
    error_log string Path to the error log folder (error.log, warn.log). A file larger than 10 MB is renamed to <name>.1 (one previous copy is kept), so logs take no more than ~40 MB
    json string Path to the JSON data folder (used by FileAdapter)
    db IAppDB Database connection parameters
    isLocalStorage boolean Use the platform's local storage instead of a database
    memorySession IMemorySessionConfig | false An in-process userData session for Telegram/VK/MAX/Viber without a database when isLocalStorage: true. Defaults to { maxSize: 10000, ttl: 86400000 }; false disables it
    env string Path to the .env file OR the string 'local' for process.env
    tokens ITokenPlatform Platform tokens (for adapters, if not passed via the constructor)

    Nested types:

    interface IAppDB {
    host: string; // for example, 'mongodb://localhost:27017'
    user?: string;
    pass?: string; // Note: the field is called pass, not password
    database: string;
    options?: Record<string, unknown>;
    }

    interface ITokenPlatform {
    [platform: string]: {
    token?: string;
    // speech_kit_token — for TTS in Telegram/VK/Max
    // (passed through the index signature below, not declared explicitly in the interface)
    [key: string]: string | number | undefined;
    };
    }

    Important. speech_kit_token is not declared explicitly in ITokenPlatform — it is passed through the index signature. At the TypeScript level this works: appConfig.tokens.telegram.speech_kit_token has the type string | number | undefined.

    bot.setAppConfig({
    json: './data', // folder for JSON files (FileAdapter)
    error_log: './errors', // folder for logs
    isLocalStorage: true, // local storage (for voice platforms)
    env: '.env', // path to the file with tokens
    db: {
    // MongoDB connection (if not isLocalStorage)
    host: 'mongodb://localhost:27017',
    database: 'umbot',
    },
    });

    Telegram/VK/MAX/Viber have no local storage: with isLocalStorage: true and no DB adapter, userData is kept in process memory (memorySession). The data is lost on restart and is not shared between processes, replicas and serverless function calls — for production with dialog steps, connect a database.


    setPlatformParams takes an IAppParam object:

    Field Type Description
    intents IAppIntent[] | null Required. The list of intents for command recognition
    welcome_text string | string[] The greeting text (when messageId === 0)
    help_text string | string[] The help text (for the "help" command)
    empty_text string | string[] The text when no command matches
    isAuthUser boolean Whether user authorization is required
    utm_text string | null UTM tags for links (with null, utm_source=umbot&utm_medium=cpc&utm_campaign=phone is automatically added to link buttons without UTM; a string replaces these tags entirely)

    Intent:

    interface IAppIntent {
    name: string;
    slots: (string | RegExp)[]; // string → substring; RegExp → .test()
    is_pattern?: boolean; // treat strings as regex (false by default)
    }
    bot.setPlatformParams({
    welcome_text: 'Hi! I can count.',
    help_text: 'This is a math game.',
    empty_text: 'I didn\'t get that. Say "help".',
    intents: [
    { name: 'game', slots: ['game', 'start game'] },
    { name: 'bye', slots: ['bye', 'goodbye'] },
    { name: 'phone', slots: ['\\+?\\d{11}'], is_pattern: true }, // a slot as regex
    ],
    });

    Important: the intents field is required even if it is empty: intents: []. Without it TypeScript reports a type error.


    const ctx = bot.getAppContext();

    ctx.appConfig; // the filled IAppConfig (with all defaults)
    ctx.platformParams; // IAppParam
    ctx.platforms; // platform registry { alisa: AlisaAdapter, telegram: ... }
    ctx.database.adapter; // the active DB adapter
    ctx.command; // CommandReg (command registry)
    ctx.httpClient; // the fetch function (can be overridden)
    ctx.log('...'); // log
    ctx.logError('msg', { error: 'details' });
    ctx.logWarn('msg', { warning: 'details' });
    ctx.logMetric('name', value, { platform: 'telegram' });

    Mode Logs ReDoS check When to use
    dev Detailed Warns but does not block Development, BotTest
    prod Minimal Warns but does not block (the mode is unsafe, kept for backward compatibility) Pre-prod
    strict_prod Minimal Blocks dangerous ones Production

    The default mode. Until setAppMode() is called, the mode is determined by the NODE_ENV environment variable: NODE_ENV=production — strict_prod, otherwise — dev (Express and frontend bundlers rely on NODE_ENV the same way). An explicit setAppMode() always takes precedence. The Docker image generated by the CLI sets NODE_ENV=production.

    What strict_prod does:

    • Disables dangerous RegExps. When a command with a potentially vulnerable regular expression is registered, the framework logs an error and excludes the dangerous slots: if safe slots remain, the command is registered with them (it no longer matches by the vulnerable expression, no exception is thrown). If ALL of the command's slots are dangerous, the command is not registered at all. In the dev and prod modes dangerous RegExps are used as is: with a warning if re2 is installed, and with an error in the logs if not (without re2 the built-in Node engine is vulnerable to catastrophic backtracking).
    • Reduces logging. Only errors and warnings.

    Secret masking in logs (tokens and passwords are replaced with ***) works in all modes and is disabled only by a custom logger with maskSecrets: false.


    The webhook is the only entry point of your application. Until signature verification is enabled, anyone who learns the webhook URL can send requests on behalf of any user: bypass authorization by userId, read and overwrite other users' userData, control other users' dialog steps and spend API quotas. The webhook URL is not a secret (it is visible to the platform, logs, domain registries), so the signature is not an option but a mandatory configuration step.

    Signature verification is enabled automatically as soon as a secret is set — there is nothing else to "turn on". The framework warns in the log at startup if no secret is set for a connected platform.

    Platform What to set Where the secret lives on the platform side
    Telegram TELEGRAM_WEBHOOK_SECRET / tokens.telegram.webhookSecret the secret_token field when calling setWebhook
    VK tokens.vk.secret_key (or vk_secret_key) the "Secret key" setting in the group settings (VK Callback API)
    MAX MAX_WEBHOOK_SECRET / tokens.max_app.webhookSecret (or secret) the secret field in POST /subscriptions
    Viber tokens.viber.token the bot token — it is also the HMAC key (x-viber-content-signature)
    Alice, SmartApp, Marusia — the platform does not sign requests at all (see below)

    For Telegram and MAX the easiest way is one command in the project folder: it takes the token from .env, generates a secret, registers the webhook with it right away and saves the secret to .env (TELEGRAM_WEBHOOK_SECRET / MAX_WEBHOOK_SECRET). The secret is written only after a successful registration, so the values in .env and on the platform do not diverge. If .env already has a secret, it is used.

    npx umbot webhook telegram https://your-domain/webhook
    npx umbot webhook max https://your-domain/webhook # MAX: HTTPS on port 443 only

    After the command, restart the bot — signature verification turns on automatically.

    You can generate a secret manually with any command:

    node -e "console.log(require('crypto').randomBytes(24).toString('base64url'))"
    # or: openssl rand -base64 24
    # 1. The secret is the same string in both places
    curl "https://api.telegram.org/bot<TOKEN>/setWebhook" \
    -d "url=https://your-domain/webhook" \
    -d "secret_token=<SECRET>"
    // 2. The same secret in the application configuration: TELEGRAM_WEBHOOK_SECRET in .env/the environment
    // is picked up automatically, or explicitly:
    bot.use(new TelegramAdapter('YOUR_BOT_TOKEN'));
    bot.setAppConfig({
    tokens: {
    telegram: { token: 'YOUR_BOT_TOKEN', webhookSecret: process.env.TELEGRAM_WEBHOOK_SECRET },
    },
    });

    Without webhookSecret, the adapter accepts any request with an update_id field — this is acceptable only for local debugging. With a secret set, requests without the x-telegram-bot-api-secret-token header (or with a wrong value) are rejected with 401 before any logic runs.

    Enable the "Secret key" in the group settings (Manage → API usage → Callback API) and pass the same value:

    bot.use(new VkAdapter('YOUR_VK_TOKEN', { vk_secret_key: 'YOUR_SECRET' }));
    // or via the config: tokens.vk.secret_key

    VK sends secret in the body of every callback request; the adapter compares it in constant time (timingSafeEqual). If the secret is not enabled in the group, verification cannot be enabled (there is nothing to compare against), see ipFilter below.

    MAX passes the secret in the x-max-bot-api-secret header:

    bot.use(new MaxAdapter('YOUR_MAX_TOKEN', { secret: 'YOUR_SECRET' }));
    // or via the config: tokens.max_app.webhookSecret

    Alice, SmartApp and Marusia provide no webhook signature mechanism — the entire payload, including user_id, is controlled by the sender of the request. This is a platform limitation, not a framework one:

    • never treat a voice platform's userId as an authenticated identity;
    • for sensitive data, add your own user verification (a PIN code, linking an external account);
    • do not store data in voice platforms' userData whose loss or substitution is critical.

    An additional layer for any platform is ipFilter (see middleware.md): restricting incoming requests to the platforms' IP ranges (for example, for Telegram only: 149.154.160.0/20, 91.108.4.0/22).


    The framework supports re2. Using this library significantly speeds up regular expression processing and reduces memory usage. Memory usage drops roughly 3-7 times, and execution time drops 2-15 times on average.

    npm install re2
    

    The framework automatically detects whether re2 is installed and uses it.

    Using the file database in a release version of the application is not recommended, since it can lead to the application crashing with a large number of records. This is because the file database keeps the data in RAM.

    To save data to a database correctly:

    1. Connect a ready-made adapter (for example MongoAdapter), or create your own (bot.use(new MyAdapter()))
    2. Specify the database connection data in bot.setAppConfig({db:{...}}), or in the constructor when connecting the adapter.

    1. Check that the .env file exists at the specified path
    2. Variables can have spaces around = — the parser trims them (TELEGRAM_TOKEN = abc works the same as TELEGRAM_TOKEN=abc; quotes around the value are allowed — the parser removes them)
    3. Enable the dev mode for detailed logs: bot.setAppMode('dev')
    1. Check the db.host format: it must include the protocol (mongodb://localhost:27017, not localhost:27017)
    2. Make sure MongoDB is running and reachable
    3. Check the logs: error_log shows the connection error
    1. Make sure intents is passed to setPlatformParams (even if empty: intents: [])
    2. Slots must be in lower case (like userCommand)
    3. Enable the dev mode to see the command lookup process

    More about configuration (IAppConfig, IAppParam), token priority and the contents of .env — in the Configuration: IAppConfig and IAppParam section.