kappelas / kappelas-sdk-php
Official PHP SDK for the Kappela messaging platform
Requires
- php: >=8.1
- guzzlehttp/guzzle: ^7.0
- textalk/websocket: ^1.6
Requires (Dev)
- phpunit/phpunit: ^10.0
README
Official PHP SDK for the Kappela messaging platform.
Build bots and personal automations — send messages, handle events, manage chats.
Table of contents
- Prerequisites
- Install
- Quick start
- Events — WebSocket vs Webhook
- API reference
- Keyboards
- Error handling
- File input
Prerequisites
You need a bot token from BotMother, the official Kappela bot manager.
- Open Kappela and start a conversation with BotMother
- Follow the instructions to create a bot
- BotMother gives you a token — keep it secret, it gives full control over your bot
For personal automation (sending messages as yourself), generate an API key from your Kappela account settings (sk_...).
Install
composer require kappelas/kappelas-sdk-php
Requires PHP 8.1+.
Quick start
Bot
<?php require_once 'vendor/autoload.php'; use Kappelas\KappelaBot; use Kappelas\Types\Message; use Kappelas\Types\CallbackQuery; $bot = new KappelaBot('YOUR_BOT_TOKEN'); $bot->onMessage(function (Message $msg) use ($bot) { $bot->messages->send([ 'chat_id' => $msg->chatId, 'text' => 'Echo: ' . $msg->text, ]); }); $bot->onCallbackQuery(function (CallbackQuery $cb) use ($bot) { $bot->messages->send([ 'chat_id' => $cb->chatId, 'text' => 'You clicked: ' . $cb->callbackData, ]); }); $bot->start(); // blocks — runs the WebSocket loop
Personal automation
use Kappelas\KappelaUser; use Kappelas\Types\Message; $me = new KappelaUser('sk_...'); $me->onMessage(function (Message $msg) { if ($msg->text !== null) { echo "[{$msg->chatId}] {$msg->text}\n"; } }); $me->start();
Events — WebSocket vs Webhook
| Mode | Method | Best for |
|---|---|---|
| WebSocket | $bot->start() |
Development, local scripts |
| Webhook | $bot->webhooks->set() + $bot->handleWebhook() |
Production servers |
The same onMessage and onCallbackQuery handlers work in both modes — no code change needed when switching.
WebSocket (development)
$bot = new KappelaBot('YOUR_BOT_TOKEN'); $bot->onMessage(fn(Message $msg) => /* ... */); $bot->onCallbackQuery(fn(CallbackQuery $cb) => /* ... */); $bot->start(); // blocking
Webhook (production)
$bot = new KappelaBot('YOUR_BOT_TOKEN'); // Register once $bot->webhooks->set(['url' => 'https://your-server.com/kappela-webhook']); $bot->onMessage(fn(Message $msg) => /* ... */); $bot->onCallbackQuery(fn(CallbackQuery $cb) => /* ... */); // In your HTTP handler (e.g. Laravel route, plain PHP): $payload = json_decode(file_get_contents('php://input'), true); $bot->handleWebhook($payload); http_response_code(200);
Do not call
$bot->start()in webhook mode.
Event reference
| Method | Signature | Description |
|---|---|---|
onMessage |
callable(Message) |
Incoming message of any type |
onCallbackQuery |
callable(CallbackQuery) |
Inline button clicked by a user |
onConnected |
callable() |
WebSocket connected or reconnected |
onDisconnected |
callable(int $code, string $reason) |
WebSocket disconnected |
onError |
callable(Throwable) |
Connection or transport error |
CallbackQuery fields
$bot->onCallbackQuery(function (CallbackQuery $cb) { $cb->chatId // int — chat where the button was clicked $cb->senderId // string — UUID of the user who clicked $cb->senderNom // ?string — display name (e.g. "Arnel LAWSON") $cb->senderUsername // ?string — username (e.g. "arnell") $cb->callbackData // string — value set on the button $cb->sentAt // ?int — Unix timestamp (seconds) });
API reference
Constructor options
new KappelaBot(string $token, ...)
| Parameter | Default | Description |
|---|---|---|
$token |
— | Bot token from BotMother |
$baseUrl |
https://api.kappelas.com |
Override API base URL |
$maxRetries |
2 |
HTTP retry count on 429 / 5xx |
$timeout |
30.0 |
Per-request timeout (seconds) |
$wsMaxRetries |
12 |
Max WebSocket reconnect attempts |
new KappelaUser(string $apiKey, ...)
Same parameters — pass your sk_... API key as $apiKey.
messages
$bot->messages->send(array $params) → SendResult
$result = $bot->messages->send([ 'chat_id' => 42, 'text' => 'Hello!', 'reply_to_id' => 123, // optional — reply to a message 'reply_markup' => [ 'inline_keyboard' => [[ ['text' => 'Yes', 'callback_data' => 'yes'], ['text' => 'No', 'callback_data' => 'no'], ]], ], ]); // → SendResult{messageId, createdAt}
$bot->messages->sendPhoto(array $params) → SendMediaResult
$data = file_get_contents('banner.png'); $result = $bot->messages->sendPhoto([ 'chat_id' => 42, 'file' => ['data' => $data, 'filename' => 'banner.png', 'content_type' => 'image/png'], 'caption' => 'Check this out!', ]); // → SendMediaResult{messageId, createdAt, mediaId}
$bot->messages->sendVideo / sendDocument / sendAudio → SendMediaResult
Same shape — pass the appropriate file array.
$bot->messages->sendCarousel(array $params) → SendCarouselResult
$bot->messages->sendCarousel([ 'chat_id' => 42, 'text' => 'Pick a product:', 'carousel' => [ ['id' => 'p1', 'title' => 'Widget A', 'button_text' => 'Buy'], ['id' => 'p2', 'title' => 'Widget B', 'button_text' => 'Buy'], ], 'quick_reply_buttons' => ['See more', 'Cancel'], ]);
$bot->messages->edit(array $params) → EditMessageResult
// Edit text $bot->messages->edit([ 'chat_id' => 42, 'message_id' => 123, 'new_text' => 'Updated!', ]); // Edit inline keyboard only $bot->messages->edit([ 'chat_id' => 42, 'message_id' => 123, 'new_extra_data' => [ 'inline_keyboard' => [[['text' => 'Done ✅', 'callback_data' => 'done']]], ], ]); // → EditMessageResult{edited: true, messageId: 123}
$bot->messages->sendTyping(array $params) → TypingResult
$bot->messages->sendTyping(['chat_id' => 42]); // show $bot->messages->sendTyping(['chat_id' => 42, 'is_typing' => false]); // hide
$bot->messages->delete(array $params) → DeleteResult
$bot->messages->delete(['chat_id' => 42, 'message_id' => 123]); // → DeleteResult{deleted: true}
chats
$bot->chats->list(array $params) → ChatsResult
$result = $bot->chats->list(['limit' => 20, 'offset' => 0]); // → ChatsResult{chats: Chat[], hasMore: bool}
$bot->chats->iterate(int $pageSize, callable $fn) → void
$bot->chats->iterate(50, function (Chat $chat): bool { echo $chat->chatId . ' ' . $chat->type . "\n"; return true; // return false to stop early });
webhooks
$bot->webhooks->set(array $params) → WebhookSetResult
$bot->webhooks->set(['url' => 'https://your-server.com/kappela-webhook']);
$bot->webhooks->getInfo() → WebhookInfo
$info = $bot->webhooks->getInfo(); // → WebhookInfo{active: true, url: '...', createdAt: 1234567890}
$bot->webhooks->delete() → WebhookDeleteResult
$bot->webhooks->delete(); // → WebhookDeleteResult{active: false}
profile
$bot->profile->get() → BotProfile
$profile = $bot->profile->get(); // → BotProfile{userId, username, isBot: true, about, description, avatarUrl}
$me->profile->get() → UserProfile
$profile = $me->profile->get(); // → UserProfile{id, username, nom, isBot: false, isPremium, avatarUrl}
Keyboards
Three types of keyboard can be passed as reply_markup on any send* call:
// Inline buttons — attached to the message $inline = [ 'inline_keyboard' => [[ ['text' => 'Yes', 'callback_data' => 'yes'], ['text' => 'No', 'callback_data' => 'no'], ]], ]; // Reply keyboard — shown below the input bar $reply = [ 'keyboard' => [ ['Option A', 'Option B'], ['Cancel'], ], ]; // Scroll keyboard — horizontal scrollable chips $scroll = [ 'scroll_keyboard' => ['Small', 'Medium', 'Large'], ]; $bot->messages->send([ 'chat_id' => 42, 'text' => 'Pick one:', 'reply_markup' => $inline, ]);
Error handling
All API errors throw a KappelaError with structured fields:
use Kappelas\KappelaError; try { $bot->messages->send(['chat_id' => 999, 'text' => 'Hi']); } catch (KappelaError $e) { $e->code // e.g. KappelaError::NOT_FOUND $e->status // 404 $e->errorMessage // server error message $e->requestId // mention this when contacting support echo $e->getMessage(); // full formatted block with hints and solutions }
Error codes
| Constant | HTTP | Meaning |
|---|---|---|
KappelaError::UNAUTHORIZED |
401 | Token or API key invalid / expired |
KappelaError::FORBIDDEN |
403 | Missing permission or role |
KappelaError::NOT_FOUND |
404 | Resource does not exist |
KappelaError::MISSING_FIELD |
400 | Required parameter missing |
KappelaError::INVALID_FIELD |
400 | Parameter has wrong type or format |
KappelaError::CONFLICT |
409 | Resource already exists |
KappelaError::METHOD_NOT_ALLOWED |
405 | Wrong HTTP method |
KappelaError::INVALID_PATH |
404 | API path does not exist |
KappelaError::INTERNAL_ERROR |
500 | Unexpected server error |
KappelaError::SERVICE_UNAVAILABLE |
503 | Service temporarily down |
KappelaError::UPSTREAM_ERROR |
502 | Upstream service error |
File input
Media methods accept a file array:
$file = [ 'data' => string, // raw binary content 'filename' => string, // e.g. 'photo.jpg' 'content_type' => string, // e.g. 'image/jpeg' ];
// From disk $bot->messages->sendPhoto([ 'chat_id' => 42, 'file' => [ 'data' => file_get_contents('photo.jpg'), 'filename' => 'photo.jpg', 'content_type' => 'image/jpeg', ], ]); // From memory $bot->messages->sendDocument([ 'chat_id' => 42, 'file' => [ 'data' => $pdfBytes, 'filename' => 'report.pdf', 'content_type' => 'application/pdf', ], ]);
License
MIT © Arnel LAWSON