Search by

xologie / botman-driver-max

MrSomFeRGO

BotMan driver for MAX Messenger (max.ru)

2.0.3 2026-09-21 07:57 UTC

This package is auto-updated.

Last update: 2026-09-21 07:58:59 UTC


README

Latest Version on Packagist License: MIT

BotMan driver for MAX Messenger. Build chatbots for MAX using the BotMan framework — just like you would for Telegram, Facebook, or Slack.

Features

  • Text messages, callback buttons, bot started events
  • Incoming media: images, video, audio, files, locations
  • Outgoing images by public HTTPS URL; local images, video, audio, and documents through the MAX upload API
  • Inline keyboards (BotMan Question/Button or fluent MaxInlineKeyboard builder)
  • Conversations with interactive button replies
  • Typing indicator
  • Webhook signature verification (X-Max-Bot-Api-Secret)
  • Explicit-recipient/stateless sends with a typed result and verified message ID
  • Typed update, explicit callback answer, and HMAC-verified contact helpers
  • Status/header-preserving cURL transport with explicit timeouts and no retry/redirect
  • Lifecycle events (message_edited, bot_added, user_removed, etc.)
  • Laravel auto-discovery & Artisan commands

Requirements

  • PHP 8.1+
  • BotMan 2.x
  • Laravel 10, 11, 12 or 13 (optional, for ServiceProvider and Artisan commands)

Installation

composer require xologie/botman-driver-max

Upgrading from 1.x

Version 2.0 deliberately does not match an existing ^1.0 constraint. The release changes media sending, delivery classification, identifiers, callbacks, transport configuration, and safe exception output. Read the complete 2.0 migration guide before changing the constraint.

Update the consuming application explicitly:

composer require xologie/botman-driver-max:^2.0

Laravel

The service provider is auto-discovered via composer.json extra. Publish the config:

php artisan vendor:publish --provider="BotMan\Drivers\Max\Providers\MaxServiceProvider"

BotMan Studio

If using BotMan Studio, the driver is auto-discovered via discovery.json. No manual setup needed.

Standalone (without Laravel)

use BotMan\BotMan\Drivers\DriverManager;
use BotMan\Drivers\Max\MaxDriver;

DriverManager::loadDriver(MaxDriver::class);

Configuration

Add to your .env:

MAX_BOT_TOKEN=your-bot-token
MAX_API_URL=https://platform-api2.max.ru
MAX_CONNECT_TIMEOUT=5
MAX_TIMEOUT=15
MAX_VERIFY_SSL=true
# The bundled MAX API CA is enabled by default. Optional overrides:
# MAX_USE_BUNDLED_CA_CERTIFICATE=false
# MAX_CA_CERTIFICATE_PATH=/path/to/combined-ca-bundle.pem
# MAX_CA_CERTIFICATE_DIRECTORY=/path/to/hashed-ca-directory
MAX_WEBHOOK_SECRET=optional-secret
MAX_WEBHOOK_URL=https://your-domain.com/botman
VariableRequiredDescription
MAX_BOT_TOKENYesBot token from @MasterBot
MAX_API_URLNoHTTPS API base URL (default: https://platform-api2.max.ru)
MAX_CONNECT_TIMEOUTNoConnection timeout in seconds (default: 5)
MAX_TIMEOUTNoTotal timeout in seconds (default: 15)
MAX_VERIFY_SSLNoVerify TLS certificate and host (default: true)
MAX_USE_BUNDLED_CA_CERTIFICATENoUse the package's Минцифры root CA for the default MAX API host (default: true)
MAX_CA_CERTIFICATE_PATHNoCustom CA bundle; takes precedence over the bundled certificate
MAX_CA_CERTIFICATE_DIRECTORYNoCustom CA directory; takes precedence over the bundled certificate
MAX_WEBHOOK_SECRETNoSecret for webhook verification via X-Max-Bot-Api-Secret header
MAX_WEBHOOK_URLNoDefault URL for artisan webhook commands
MAX_THROW_HTTP_EXCEPTIONSNoThrow exceptions on API errors (default: false)

The configured base URL must be absolute HTTPS and cannot contain credentials, a query, or a fragment. API requests carry the token only in the Authorization header. The built-in transport performs one request, does not follow redirects, and never retries automatically.

TLS certificates

MAX requires clients of platform-api2.max.ru to trust the Минцифры CA certificate. The driver enables the root CA shipped with the package for that host by default. Keep MAX_VERIFY_SSL=true; disabling verification exposes the bot token and message data to interception.

Set MAX_CA_CERTIFICATE_PATH or MAX_CA_CERTIFICATE_DIRECTORY to use your own trust bundle instead. A custom setting takes precedence over the bundled certificate and applies to all driver requests:

  • MAX_CA_CERTIFICATE_PATH must point to a readable PEM bundle. It replaces cURL's default CA file for driver requests, so the bundle must contain every required trusted root, not only the Минцифры certificate. This matters because media uploads use HTTPS hosts other than platform-api2.max.ru.
  • MAX_CA_CERTIFICATE_DIRECTORY must point to a cURL/OpenSSL-compatible hashed certificate directory, not an arbitrary directory containing certificate files.

The bundled Минцифры CA applies only to platform-api2.max.ru. Media upload hosts continue to use the system trust store because their current chains are issued by other CAs. Set MAX_USE_BUNDLED_CA_CERTIFICATE=false to use only the system trust store, for example after installing the Минцифры CA into the container image.

The bundled root is taken directly from the Gosuslugi static download; its expected SHA-256 fingerprint is recorded alongside the file. Review certificate source and rotation as part of dependency updates. Verify the resulting trust chain from the same host or container that runs the bot before deployment.

This configuration verifies outbound connections made by the driver. It does not configure the certificate presented by your webhook endpoint. MAX separately requires the webhook to use HTTPS, a non-self-signed certificate from a trusted CA, a matching CN/SAN, and a complete server certificate chain; see POST /subscriptions.

Bot Commands

To configure bot menu commands, add them to your published config/botman/max.php:

'commands' => [
    ['name' => 'help', 'description' => 'Show available commands'],
    ['name' => 'start', 'description' => 'Start the bot'],
],

Then apply them:

php artisan botman:max:commands

Usage

Stateless Send to a Chat

Use sendToChat() when sending outside the context of an incoming BotMan message:

use BotMan\Drivers\Max\Messages\MaxOutgoingMessage;

$message = MaxOutgoingMessage::create('Order accepted')
    ->replyTo('mid.original');

$result = $driver->sendToChat('0009223372036854775807', $message);

if ($result->isSent()) {
    $messageId = $result->messageId(); // verified message.body.mid
} elseif ($result->isRateLimited()) {
    $retryAfter = $result->retryAfterSeconds();
} elseif ($result->isOutcomeUnknown()) {
    // Do not blindly retry: MAX may have received the request.
}

The result also exposes httpStatus() and errorCode(). A send is successful only for HTTP 200 with valid JSON and a non-empty message.body.mid. Timeouts, connection loss, malformed success responses, and server failures are classified as an unknown outcome.

Echo Bot

$botman->hears('{message}', function ($bot, $message) {
    $bot->reply('You said: ' . $message);
});

Handling /start

The bot_started webhook event is automatically mapped to the /start text command:

$botman->hears('/start', function ($bot) {
    $bot->reply('Welcome! I am a MAX bot.');
});

The unmodified deep-link value is also available from $driver->update()->startPayload() and from the incoming message's payload extra.

Questions with Inline Buttons

use BotMan\BotMan\Messages\Outgoing\Question;
use BotMan\BotMan\Messages\Outgoing\Actions\Button;

$question = Question::create('What would you like to do?')
    ->addButton(Button::create('Order')->value('order'))
    ->addButton(Button::create('Help')->value('help'));

$botman->hears('menu', function ($bot) use ($question) {
    $bot->ask($question, function ($answer) {
        $value = $answer->getValue();
        // Handle 'order' or 'help'
    });
});

Conversations

use BotMan\BotMan\Messages\Conversations\Conversation;

class OnboardingConversation extends Conversation
{
    public function askName()
    {
        $this->ask('What is your name?', function ($answer) {
            $this->say('Nice to meet you, ' . $answer->getText());
        });
    }

    public function run()
    {
        $this->askName();
    }
}

$botman->hears('/start', function ($bot) {
    $bot->startConversation(new OnboardingConversation);
});

MaxInlineKeyboard (Fluent API)

For more control over keyboard layout, use the fluent builder:

use BotMan\Drivers\Max\Extensions\MaxInlineKeyboard;
use BotMan\Drivers\Max\Extensions\MaxKeyboardButton;

$keyboard = MaxInlineKeyboard::create()
    ->addRow(
        MaxKeyboardButton::create('Yes')->callbackData('yes'),
        MaxKeyboardButton::create('No')->callbackData('no')
    )
    ->addRow(
        MaxKeyboardButton::create('Website')->url('https://example.com')
    );

$botman->hears('vote', function ($bot) use ($keyboard) {
    $bot->reply('Cast your vote:', $keyboard->toArray());
});

Button types: callbackData(), url(), requestContact(), requestGeoLocation().

Driver Restriction

Limit a listener to MAX only:

use BotMan\Drivers\Max\MaxDriver;

$botman->hears('max only', function ($bot) {
    $bot->reply('This only works in MAX!');
})->driver(MaxDriver::class);

Receiving Attachments

$botman->receivesImages(function ($bot, $images) {
    foreach ($images as $image) {
        $bot->reply('Image: ' . $image->getUrl());
    }
});

$botman->receivesLocation(function ($bot, $location) {
    $bot->reply("Lat: {$location->getLatitude()}, Lng: {$location->getLongitude()}");
});

Also available: receivesVideos(), receivesAudio(), receivesFiles().

Typed Incoming Update and Contacts

Application code can read stable helpers instead of depending on nested webhook paths:

$update = $driver->update();

$update->type();
$update->userId();
$update->chatId();
$update->messageId();
$update->replyToMessageId();
$update->callbackId();
$update->callbackPayload();
$update->startPayload();
$update->isPrivateDialog();

if ($contact = $update->contact()) {
    $phone = $contact->phone();
    $email = $contact->email();
    $name = $contact->name();
}

contact() returns a contact only when the exact vcf_info passes the MAX HMAC-SHA256 check using the bot token. The original vCard, hash and max_info remain accessible on the typed contact.

Sending Attachments

use BotMan\BotMan\Messages\Outgoing\OutgoingMessage;
use BotMan\BotMan\Messages\Attachments\Image;

$message = OutgoingMessage::create('Check this out!')
    ->withAttachment(new Image('https://example.com/photo.jpg'));

$botman->hears('photo', function ($bot) use ($message) {
    $bot->reply($message);
});

Only images support a direct external URL, and it must use HTTPS. Since 2.0.3, MaxOutgoingMessage can upload a readable local path or stream for images, video, audio, and documents:

use BotMan\Drivers\Max\Messages\MaxOutgoingMessage;

$message = MaxOutgoingMessage::create('Clip')
    ->videoFile('/safe/local/clip.mp4', 'clip.mp4', 'video/mp4');

$result = $driver->sendToChat($chatId, $message);

Use imageFile($source, $filename, $contentType), videoFile(...), audioFile(...), or document(...). The optional filename and content type are useful for streams. Each method sends one file using multipart field data, then sends attachments: [{type, payload: {token}}]. Video and audio tokens come from POST /uploads after the upload server confirms success; image and document tokens come from the upload response. Image responses may contain the token inside photos. A message can contain one uploaded media attachment and at most one inline keyboard.

The driver never downloads an external media URL. Direct video, audio, and document URLs are rejected. The bot token is not sent to the server-issued multipart upload URL. Upload failures stop before POST /messages; only an uncertain final message request has an unknown delivery outcome. The driver does not retry automatically. See the MAX upload API for supported formats and limits.

MAX may still be processing an uploaded file when the message is sent. If the API returns attachment.not.ready, sendToChat() reports that failure through MaxSendResult; the application can decide when to retry.

Callback Handling and Message Update

Receiving a callback update is acknowledged by the HTTP 200 response from your webhook endpoint. BotMan calls messagesHandled() after the user handler, but in driver 2.0.1 this method is intentionally a no-op and does not call MAX.

Call /answers only when you explicitly want to replace the message that contained the pressed button:

$callbackId = $driver->update()->callbackId();
if ($callbackId === null) {
    throw new LogicException('The callback update has no callback ID.');
}

$result = $driver->answerCallback(
    $callbackId,
    MaxOutgoingMessage::create('Updated message'),
);

if (!$result->isSuccessful()) {
    // Observe the typed failure without logging callback or message payloads.
}

answerCallback() rejects an empty callback ID or empty message before the request. It sends the RFC 3986 encoded callback_id in the query and a non-empty message in the JSON body. Success requires both HTTP 200 and JSON success === true. The latest explicit result is available through $driver->lastCallbackResult().

Sending the next Dialogue message is a separate POST /messages operation; do not use /answers as a transport acknowledgement.

Extended User Data

The driver returns an extended User object with MAX-specific methods:

$botman->hears('whoami', function ($bot) {
    $user = $bot->getUser();
    $bot->reply('Name: ' . $user->getFullName());
    $bot->reply('Username: ' . $user->getMaxUsername());
});

Events

Listen to MAX lifecycle events:

$botman->on('bot_added', function ($payload, $bot) {
    // Bot was added to a chat
});

$botman->on('message_edited', function ($payload, $bot) {
    // A message was edited
});

Available events: message_edited, message_removed, bot_added, bot_removed, bot_stopped, user_added, user_removed, chat_title_changed, dialog_cleared, dialog_muted, dialog_removed, dialog_unmuted.

Artisan Commands

CommandDescription
php artisan botman:max:registerRegister the webhook
php artisan botman:max:unregisterRemove the webhook
php artisan botman:max:webhooksList all registered webhooks
php artisan botman:max:commandsSet bot menu commands

Options for register/unregister:

php artisan botman:max:register --url=https://your-domain.com/botman
php artisan botman:max:unregister --url=https://your-domain.com/botman

Drivers

DriverHandles
MaxDriverText messages, verified contacts, callbacks, bot_started
MaxImageDriverIncoming images
MaxVideoDriverIncoming videos
MaxAudioDriverIncoming audio
MaxFileDriverIncoming files
MaxLocationDriverIncoming locations

Testing

composer test

License

MIT