Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Max Bot Api Client Php Laravel Package

bushlanov-dev/max-bot-api-client-php

bushlanov-dev/max-bot-api-client-php is a Composer-ready PHP client for the MAX Bot API, making it easy to build bots for the MAX messenger. It offers convenient methods for messages, media, keyboards, updates, and webhooks, with a clean, framework-agnostic design.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install and discover the entry point

composer require bushlanov-dev/max-bot-api-client-php

There is no Laravel service provider in this package — it's a plain PHP SDK. Read, in this order:

  1. README.md — the constructor signature and the "quick start" snippet tell you the auth model (a bot token, plus the base URI https://botapi.max.ru by default).
  2. src/ — find the client class (namespace is BushlanovDev\MaxBotApi\…) and the sibling directory holding the request/response DTOs. That DTO directory is the API surface; skim it before writing anything.
  3. composer.json — check the PHP constraint and whether it pulls Guzzle, PSR-18, or both. This determines how you test it later.

2. Get a token

Create the bot in MAX itself (the @MasterBot flow) and copy the access token. Put it in .env, never in a committed config:

MAX_BOT_TOKEN=xxxxxxxx
MAX_BOT_API_BASE_URI=https://botapi.max.ru

3. Wire it into the container

// config/services.php
'max_bot' => [
    'token'    => env('MAX_BOT_TOKEN'),
    'base_uri' => env('MAX_BOT_API_BASE_URI', 'https://botapi.max.ru'),
],
// app/Providers/AppServiceProvider.php  (register() or boot())
$this->app->singleton(Client::class, fn () => new Client(
    token: config('services.max_bot.token'),
    baseUri: config('services.max_bot.base_uri'),
));

The client is stateless HTTP, so a singleton is correct and gives you connection reuse.

4. First useful call

$me = app(Client::class)->getMe();   // sanity check: confirms the token works
$client->sendMessage(chatId: $chatId, text: 'Hello from Laravel');

If getMe() returns, everything else is just picking the right DTO from src/. Start with an echo bot (receive an update → send the same text back) before building anything with keyboards or attachments.

Implementation Patterns

Wrap it, don't sprinkle it. Because there is no framework integration, define a thin seam so your app doesn't depend on the SDK's method names everywhere:

interface MaxBot
{
    public function send(int $chatId, string $text, array $opts = []): void;
}

final class SdkMaxBot implements MaxBot
{
    public function __construct(private Client $client) {}

    public function send(int $chatId, string $text, array $opts = []): void
    {
        $this->client->sendMessage(chatId: $chatId, text: $text, ...$opts);
    }
}

Bind MaxBot to SdkMaxBot. This pays for itself the first time you need to swap a transport, add retry logic, or fake the bot in a feature test.

Two ingestion strategies — pick one per bot:

Strategy Fits Laravel shape
Long polling (getUpdates in a loop) Local dev, single-server bots, no public HTTPS php artisan max:listen command, run under Supervisor
Webhook (subscription to a callback URL) Production, horizontal scale Route + controller, VerifyCsrfToken excepted, registered once via the client's subscription method

The polling command is the classic shape:

public function handle(Client $client): int
{
    $marker = Cache::get('max:marker');

    while (true) {
        $updates = $client->getUpdates(marker: $marker, timeout: 30);
        foreach ($updates as $update) {
            ProcessMaxUpdate::dispatch($update);
            $marker = $update->marker ?? $marker;
        }
        Cache::forever('max:marker', $marker);
    }
}

Persisting the marker to the cache (or DB) matters — in-process state disappears on every deploy.

Never send from the request/update handler. Dispatch a job. The API has per-chat and global rate limits, and a slow send inside a webhook handler will make MAX retry the update and duplicate your message.

final class SendMaxMessage implements ShouldQueue
{
    use Queueable;
    public $tries = 5;
    public $backoff = [1, 5, 15, 60];

    public function handle(MaxBot $bot): void
    {
        $bot->send($this->chatId, $this->text);
    }
}

Laravel notifications. A custom channel is the highest-leverage integration — it makes the bot usable from anywhere in the app:

final class MaxChannel
{
    public function send($notifiable, Notification $notification): void
    {
        $message = $notification->toMax($notifiable);
        SendMaxMessage::dispatch($notifiable->max_chat_id, $message->text);
    }
}

Add a routeNotificationForMax() on your User model returning the stored chat_id. Note that a MAX bot can only message a chat the user has already started — collect the chat_id during onboarding via a deep link / start payload, not by guessing a phone number.

Multi-bot / multi-tenant. If you run more than one bot, don't use a singleton. Use a factory keyed by tenant and request-scoped binding, or Client instances constructed on demand from a bots table. Cache nothing tenant-specific in the singleton.

Attachments and keyboards. These are DTO objects, not arrays. Build them once in a small MaxKeyboard::main() helper rather than inline in every job — the payloads are nested and easy to get subtly wrong, and the error you get back will be a generic 400.

Gotchas and Tips

  • This is a 51-star, zero-dependent package. That's a real signal: no Laravel ecosystem, few Stack Overflow answers, and no community pressure on breaking changes. Pin the version in composer.json ("bushlanov-dev/max-bot-api-client-php": "1.*" or exact) and read CHANGELOG/git tags before composer update. Treat the SDK's DTO properties as semi-public API and adapt at your MaxBot seam.
  • The README is the docs. There is no separate site. When something misbehaves, read the source in vendor/bushlanov-dev/…/src/ — you'll find the exact request body and endpoint in under a minute, which is faster than guessing.
  • Long polling blocks a worker. Run it as a dedicated Supervisor program with numprocs=1 and --timeout=0. Running it as a queued job or inside a schedule:run will either time out or spawn overlapping pollers, and two pollers with the same token fight over the marker and silently drop updates.
  • Deduplicate inbound updates. Both webhook retries and poller restarts cause redelivery. Keep a max_update_id unique index and insertOrIgnore before processing; the alternative is users seeing the same reply twice.
  • Token leakage. SDKs commonly echo the request URI in exception messages, and the token is often a query param in MAX-style APIs. Wrap calls in try/catch, log $e->getMessage() only after stripping query strings, and never Log::info($request) in the client wrapper. Also exclude the token from config:cache output and from Horizon/Telescope payloads if you pass DTOs around.
  • Timeouts. Set an explicit HTTP timeout (10s for sends, 40s+ for getUpdates long polls). The default Guzzle timeout of 0 will hang a worker forever on a half-open connection, and that looks exactly like "the bot stopped responding."
  • Retries need backoff, not retry(3). MAX returns rate-limit errors under load. Use backoff() in the job, and make sends idempotent-ish by only retrying on transport/5xx/429 errors — retrying on a 4xx (bad chat id, bot blocked) burns your queue.
  • Webhook security. If the package/client supports a secret-token header on subscription setup, use it and verify in the controller with hash_equals. If it doesn't, restrict the endpoint by path secret plus IP allowlist, and return 200 quickly — a non-2xx response triggers redelivery storms.
  • Test at the seam, not the SDK. The client may not be built on Laravel's Http facade, so Http::fake() won't intercept it. Fake MaxBot in feature tests (assert a job was dispatched, or use a recording fake) and keep one thin integration test that hits the real getMe() in a nightly/manual job.
  • Extension points to use deliberately: a PSR-18/Guzzle client injected into the SDK constructor is the ideal place to add a logging middleware, a retry middleware, and a metrics middleware — one wire-up covers every API call. That is almost always cleaner than wrapping each method.
  • PHP/ext constraints. Check composer.json for ext-curl/ext-json and the minimum PHP version before adding it to a legacy app; the failure mode is a composer require conflict that looks like a dependency-resolution mess rather than a version constraint.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core