Bot API allows you to create bots for the WWChat messenger.
Getting Started
Base URL
https://api.wwchat.org/bot/v1/{token}/ Authentication
The bot authenticates using a token in the URL. The token is issued when creating a bot.
Token format: {user_id}:{random_string}
Example: 550e8400-e29b-41d4-a716-446655440000:aB3dE5fG7hJ9kL1mN3pQ5rS7tU9vW1xY3zA5bC7dE9fG1
Creating a Bot
Via @BotMama:
- Open a chat with @BotMama
- Send the command
/newbot - Enter the bot name
- Enter username (must end with
bot) - Receive your token
Response Format
All methods return JSON:
{
"ok": true,
"result": { ... }
} On error:
{
"ok": false,
"error_code": 400,
"description": "Error message"
} API Methods
getMe
Get information about the bot.
Request: GET /bot/v1/{token}/getMe
{
"ok": true,
"result": {
"id": "uuid",
"username": "MyBot",
"description": "Bot description",
"is_bot": true,
"can_join_groups": true,
"can_read_group_messages": true,
"can_join_channels": false
}
} sendMessage
Send a message.
Request: POST /bot/v1/{token}/sendMessage
| Parameter | Type | Required | Description |
|---|---|---|---|
| chat_id | String | Yes | Chat UUID or @username |
| text | String | Yes | Message text |
| parse_mode | String | No | Formatting: HTML or Markdown |
| reply_to_message_id | String | No | Message UUID to reply to |
| disable_notification | Boolean | No | Disable notification |
| reply_markup | InlineKeyboardMarkup | No | Inline keyboard |
Example with inline keyboard:
{
"chat_id": "550e8400-e29b-41d4-a716-446655440000",
"text": "Choose an action:",
"reply_markup": {
"inline_keyboard": [
[
{"text": "Button 1", "callback_data": "btn1"},
{"text": "Button 2", "callback_data": "btn2"}
],
[
{"text": "Open website", "url": "https://example.com"}
]
]
}
} getUpdates
Get updates (long polling).
Request: GET /bot/v1/{token}/getUpdates
| Parameter | Type | Description |
|---|---|---|
| offset | Integer | ID of the first update to return |
| limit | Integer | Max number (1-100) |
| timeout | Integer | Long polling timeout (max 60 sec) |
| allowed_updates | Array | Filter update types |
Important: After receiving updates, send the next request with offset = last_update_id + 1 to confirm receipt.
editMessageText
Edit message text.
Request: POST /bot/v1/{token}/editMessageText
| Parameter | Type | Required | Description |
|---|---|---|---|
| chat_id | String | Yes | Chat UUID |
| message_id | String | Yes | Message UUID |
| text | String | Yes | New text |
| reply_markup | InlineKeyboardMarkup | No | New keyboard |
Important: Only messages sent by the bot can be edited.
answerCallbackQuery
Respond to inline button press.
Request: POST /bot/v1/{token}/answerCallbackQuery
| Parameter | Type | Description |
|---|---|---|
| callback_query_id | String | Callback query ID (required) |
| text | String | Notification text (up to 200 chars) |
| show_alert | Boolean | Show alert instead of toast |
| cache_time | Integer | Cache time for the response (seconds) |
Important: Call this method even if you don't need to show a notification.
getChat
Get information about a chat.
Request: GET/POST /bot/v1/{token}/getChat
| Parameter | Type | Required | Description |
|---|---|---|---|
| chat_id | String | Yes | Chat UUID |
Response (private chat):
{
"ok": true,
"result": {
"id": "uuid",
"type": "private",
"username": "john_doe",
"first_name": "john_doe",
"bio": "Hello!",
"language": "ru",
"is_bot": false
}
} Response (group):
{
"ok": true,
"result": {
"id": "uuid",
"type": "group",
"title": "My Group",
"username": "mygroup",
"description": "Group description",
"members_count": 42,
"is_public": true
}
} The language field returns the user's language — useful for localizing bot messages.
Files
uploadFile
Upload a file for later sending.
Request: POST /bot/v1/{token}/uploadFile (multipart/form-data)
| Parameter | Type | Description |
|---|---|---|
| file | File | File to upload (max 50 MB) |
{
"ok": true,
"result": {
"id": "uuid",
"file_name": "document.pdf",
"file_size": 102400,
"mime_type": "application/pdf",
"media_type": "document"
}
} sendDocument
Send a document.
Request: POST /bot/v1/{token}/sendDocument
| Parameter | Type | Required | Description |
|---|---|---|---|
| chat_id | String | Yes | Chat UUID |
| document | String | Yes | file_id of uploaded file |
| caption | String | No | Document caption |
| parse_mode | String | No | Caption formatting |
| reply_markup | InlineKeyboardMarkup | No | Inline keyboard |
sendPhoto
Send a photo.
Request: POST /bot/v1/{token}/sendPhoto
| Parameter | Type | Required | Description |
|---|---|---|---|
| chat_id | String | Yes | Chat UUID |
| photo | String | Yes | file_id of uploaded image |
| caption | String | No | Photo caption |
| parse_mode | String | No | Caption formatting |
| reply_markup | InlineKeyboardMarkup | No | Inline keyboard |
sendVoice
Send a voice message.
Request: POST /bot/v1/{token}/sendVoice
| Parameter | Type | Required | Description |
|---|---|---|---|
| chat_id | String | Yes | Chat UUID |
| voice | String | Yes | file_id of audio file |
| caption | String | No | Caption |
| duration | Integer | No | Duration in seconds |
| reply_markup | InlineKeyboardMarkup | No | Inline keyboard |
Star Payments
Bots can accept payments in stars.
Payment Flow
- Bot calls
sendInvoice— user sees a message with "Pay" button - User clicks "Pay" — client shows a modal with details
- User confirms — server sends
pre_checkout_queryupdate to the bot - Bot calls
answerPreCheckoutQuery(ok: true/false) — within 10 seconds - If ok=true — stars are deducted from user and credited to bot
- Bot receives update with
successful_paymentin message
sendInvoice
Send an invoice for star payment.
Request: POST /bot/v1/{token}/sendInvoice
| Parameter | Type | Required | Description |
|---|---|---|---|
| chat_id | String | Yes | Chat UUID or @username |
| title | String | Yes | Product/service name (up to 255 chars) |
| description | String | Yes | Description (up to 1000 chars) |
| amount | Float | Yes | Amount in stars (> 0) |
| photo_url | String | No | Product image URL |
| payload | String | Yes | Bot data (up to 512 chars, not visible to user) |
Example:
{
"chat_id": "550e8400-e29b-41d4-a716-446655440000",
"title": "Premium Subscription",
"description": "Access to premium features for 30 days",
"amount": 10.0,
"photo_url": "https://example.com/premium.png",
"payload": "premium_30d_user123"
} answerPreCheckoutQuery
Respond to a pre-checkout query. The bot must respond within 10 seconds, otherwise the payment will be cancelled.
Request: POST /bot/v1/{token}/answerPreCheckoutQuery
| Parameter | Type | Required | Description |
|---|---|---|---|
| pre_checkout_query_id | String | Yes | UUID from pre_checkout_query update |
| ok | Boolean | Yes | true — confirm, false — decline |
| error_message | String | No | Error message (when ok=false) |
Example (confirm):
{
"pre_checkout_query_id": "550e8400-e29b-41d4-a716-446655440000",
"ok": true
} Example (decline):
{
"pre_checkout_query_id": "550e8400-e29b-41d4-a716-446655440000",
"ok": false,
"error_message": "Item out of stock"
} getStarBalance
Get the bot's star balance.
Request: GET/POST /bot/v1/{token}/getStarBalance
{
"ok": true,
"result": {
"balance": 150.5,
"pending": 0
}
} Webhooks
setWebhook
Set webhook for receiving updates.
Request: POST /bot/v1/{token}/setWebhook
| Parameter | Type | Description |
|---|---|---|
| url | String | HTTPS webhook URL (required) |
| secret_token | String | Secret for X-WWChat-Bot-Api-Secret-Token header |
| max_connections | Integer | Max connections (1-100) |
| allowed_updates | Array | Filter update types |
deleteWebhook
Request: POST /bot/v1/{token}/deleteWebhook
getWebhookInfo
Request: GET /bot/v1/{token}/getWebhookInfo
Webhook Request Format
POST https://mybot.example.com/webhook
Content-Type: application/json
X-WWChat-Bot-Api-Secret-Token: my_secret_123
{
"update_id": 123456789,
"message": { ... }
} Your server must return HTTP 200 OK. After 10 consecutive errors, the webhook is automatically disabled.
Important: When a webhook is active, getUpdates is unavailable (returns 409 Conflict).
Text Formatting
Methods sendMessage, editMessageText, sendDocument, sendPhoto support the parse_mode parameter.
HTML
{
"text": "<b>bold</b>, <i>italic</i>, <u>underline</u>, <s>strikethrough</s>, <code>code</code>, <pre>code block</pre>, <a href=\"url\">link</a>",
"parse_mode": "HTML"
} Tags: <b>, <strong>, <i>, <em>, <u>, <s>, <code>, <pre>, <a href="url">
Markdown
{
"text": "**bold**, *italic*, `code`, ~~strikethrough~~, [link](https://example.com)",
"parse_mode": "Markdown"
} Object Types
Update
{
"update_id": 123456789,
"message": { ... }, // new message
"callback_query": { ... }, // inline button press
"channel_post": { ... }, // new channel post
"my_chat_member": { ... }, // bot added/removed from chat
"pre_checkout_query": { ... } // payment confirmation request
} An Update contains one of the event types.
Message
{
"message_id": "uuid",
"from": { "id": "uuid", "username": "john", "is_bot": false },
"chat": { "id": "uuid", "type": "private" },
"date": 1705123456,
"text": "Hello",
"entities": [ ... ],
"reply_to_message": { ... },
"reply_markup": { ... },
"photo": [ ... ],
"document": { ... },
"voice": { ... },
"successful_payment": { ... }
} CallbackQuery
{
"id": "unique_callback_id",
"from": { "id": "uuid", "username": "john" },
"message": { ... },
"chat_instance": "uuid",
"data": "btn1"
} PreCheckoutQuery
{
"id": "uuid",
"from": { "id": "uuid", "username": "john", "is_bot": false },
"currency": "XTR",
"total_amount": 1000,
"invoice_payload": "premium_30d_user123"
} total_amount is in units (100 units = 1 star). currency is always "XTR".
SuccessfulPayment
Contained in the successful_payment field of a Message object after payment completion.
{
"currency": "XTR",
"total_amount": 1000,
"invoice_payload": "premium_30d_user123",
"provider_payment_charge_id": "uuid"
} MyChatMember
Notification about the bot being added or removed from a group/channel.
{
"chat": { "id": "uuid", "type": "group", "title": "My Group" },
"from": { "id": "uuid", "username": "admin" },
"old_chat_member": { "status": "left", "user": { ... } },
"new_chat_member": { "status": "member", "user": { ... } }
} Statuses: member, left, kicked.
InlineKeyboardMarkup
{
"inline_keyboard": [
[
{ "text": "Button", "callback_data": "btn1" }
],
[
{ "text": "Website", "url": "https://example.com" }
]
]
} InlineKeyboardButton
| Field | Type | Description |
|---|---|---|
| text | String | Button text (required) |
| callback_data | String | Callback data (up to 64 bytes) |
| url | String | URL to open |
| pay | Boolean | true for payment button (invoice only) |
User
| Field | Type | Description |
|---|---|---|
| id | String | User UUID |
| username | String | Username |
| is_bot | Boolean | true if bot |
| is_deleted | Boolean | true if deleted |
Chat
| Field | Type | Description |
|---|---|---|
| id | String | Chat UUID |
| type | String | private, group, channel |
| title | String | Title (for groups/channels) |
| username | String | Group/channel username |
Bots in Groups and Channels
Bots can be added to groups and channels. When added, the bot receives a my_chat_member update.
Sending to Groups
POST /bot/v1/{token}/sendMessage
{ "chat_id": "@group_username", "text": "Hello group!" } chat_id supports both UUID and @username.
Sending to Channels
POST /bot/v1/{token}/sendMessage
{ "chat_id": "@channel_username", "text": "New post!" } When sending to a channel, a full post is created. sendDocument, sendPhoto, sendVoice are also supported.
Receiving Updates
- Group messages —
message(chat.type = "group") - Channel posts —
channel_post(chat.type = "channel") - Added/removed —
my_chat_member
Examples
import requests
import time
TOKEN = "550e8400-...:aB3dE5fG7h..."
BASE_URL = f"https://api.wwchat.org/bot/v1/{TOKEN}"
def get_updates(offset=None):
params = {"timeout": 30}
if offset:
params["offset"] = offset
resp = requests.get(f"{BASE_URL}/getUpdates", params=params)
return resp.json()
def send_message(chat_id, text):
resp = requests.post(f"{BASE_URL}/sendMessage", json={
"chat_id": chat_id,
"text": text
})
return resp.json()
def main():
offset = None
print("Bot started!")
while True:
result = get_updates(offset)
if not result.get("ok"):
time.sleep(5)
continue
for update in result.get("result", []):
offset = update["update_id"] + 1
if "message" in update:
msg = update["message"]
chat_id = msg["chat"]["id"]
text = msg.get("text", "")
if text.startswith("/start"):
send_message(chat_id, "Hello! I'm a WWChat bot.")
elif text.startswith("/help"):
send_message(chat_id, "Commands:\n/start\n/help")
else:
send_message(chat_id, f"You wrote: {text}")
if __name__ == "__main__":
main() const TOKEN = "550e8400-...:aB3dE5fG7h...";
const BASE_URL = `https://api.wwchat.org/bot/v1/${TOKEN}`;
async function getUpdates(offset) {
const params = new URLSearchParams({ timeout: "30" });
if (offset) params.set("offset", offset);
const resp = await fetch(`${BASE_URL}/getUpdates?${params}`);
return resp.json();
}
async function sendMessage(chatId, text) {
const resp = await fetch(`${BASE_URL}/sendMessage`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ chat_id: chatId, text }),
});
return resp.json();
}
async function main() {
let offset = null;
console.log("Bot started!");
while (true) {
const result = await getUpdates(offset);
if (!result.ok) {
await new Promise((r) => setTimeout(r, 5000));
continue;
}
for (const update of result.result) {
offset = update.update_id + 1;
if (update.message) {
const chatId = update.message.chat.id;
const text = update.message.text || "";
if (text.startsWith("/start")) {
await sendMessage(chatId, "Hello! I'm a WWChat bot.");
} else if (text.startsWith("/help")) {
await sendMessage(chatId, "Commands:\n/start\n/help");
} else {
await sendMessage(chatId, `You wrote: ${text}`);
}
}
}
}
}
main(); package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"strings"
"time"
)
const token = "550e8400-...:aB3dE5fG7h..."
const baseURL = "https://api.wwchat.org/bot/v1/" + token
type Update struct {
UpdateID int `json:"update_id"`
Message *Message `json:"message"`
}
type Message struct {
MessageID string `json:"message_id"`
From struct {
ID string `json:"id"`
Username string `json:"username"`
} `json:"from"`
Chat struct {
ID string `json:"id"`
Type string `json:"type"`
} `json:"chat"`
Text string `json:"text"`
}
type APIResponse struct {
OK bool `json:"ok"`
Result []Update `json:"result"`
}
func getUpdates(offset int) (*APIResponse, error) {
url := fmt.Sprintf("%s/getUpdates?timeout=30", baseURL)
if offset > 0 {
url += fmt.Sprintf("&offset=%d", offset)
}
resp, err := http.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var result APIResponse
json.NewDecoder(resp.Body).Decode(&result)
return &result, nil
}
func sendMessage(chatID, text string) {
body, _ := json.Marshal(map[string]string{
"chat_id": chatID,
"text": text,
})
http.Post(baseURL+"/sendMessage", "application/json",
bytes.NewReader(body))
}
func main() {
offset := 0
fmt.Println("Bot started!")
for {
result, err := getUpdates(offset)
if err != nil || !result.OK {
time.Sleep(5 * time.Second)
continue
}
for _, update := range result.Result {
offset = update.UpdateID + 1
if update.Message == nil {
continue
}
chatID := update.Message.Chat.ID
text := update.Message.Text
switch {
case strings.HasPrefix(text, "/start"):
sendMessage(chatID, "Hello! I'm a WWChat bot.")
case strings.HasPrefix(text, "/help"):
sendMessage(chatID, "Commands:\n/start\n/help")
default:
sendMessage(chatID, "You wrote: "+text)
}
}
}
} using System.Net.Http.Json;
using System.Text.Json;
const string token = "550e8400-...:aB3dE5fG7h...";
const string baseUrl = $"https://api.wwchat.org/bot/v1/{token}";
using var http = new HttpClient();
int? offset = null;
Console.WriteLine("Bot started!");
while (true)
{
var url = $"{baseUrl}/getUpdates?timeout=30";
if (offset.HasValue) url += $"&offset={offset}";
try
{
var resp = await http.GetFromJsonAsync<JsonElement>(url);
if (!resp.GetProperty("ok").GetBoolean())
{
await Task.Delay(5000);
continue;
}
foreach (var update in resp.GetProperty("result").EnumerateArray())
{
offset = update.GetProperty("update_id").GetInt32() + 1;
if (!update.TryGetProperty("message", out var msg))
continue;
var chatId = msg.GetProperty("chat").GetProperty("id").GetString()!;
var text = msg.TryGetProperty("text", out var t) ? t.GetString() ?? "" : "";
var reply = text switch
{
_ when text.StartsWith("/start") => "Hello! I'm a WWChat bot.",
_ when text.StartsWith("/help") => "Commands:\n/start\n/help",
_ => $"You wrote: {text}"
};
await http.PostAsJsonAsync($"{baseUrl}/sendMessage", new
{
chat_id = chatId,
text = reply
});
}
}
catch
{
await Task.Delay(5000);
}
} <?php
$token = "550e8400-...:aB3dE5fG7h...";
$baseUrl = "https://api.wwchat.org/bot/v1/$token";
function getUpdates(string $baseUrl, ?int $offset = null): array {
$url = "$baseUrl/getUpdates?timeout=30";
if ($offset !== null) $url .= "&offset=$offset";
$resp = file_get_contents($url);
return json_decode($resp, true);
}
function sendMessage(string $baseUrl, string $chatId, string $text): void {
$opts = [
'http' => [
'method' => 'POST',
'header' => 'Content-Type: application/json',
'content' => json_encode([
'chat_id' => $chatId,
'text' => $text,
]),
],
];
file_get_contents($baseUrl . "/sendMessage",
false, stream_context_create($opts));
}
$offset = null;
echo "Bot started!\n";
while (true) {
$result = getUpdates($baseUrl, $offset);
if (!($result['ok'] ?? false)) {
sleep(5);
continue;
}
foreach ($result['result'] as $update) {
$offset = $update['update_id'] + 1;
if (!isset($update['message'])) continue;
$chatId = $update['message']['chat']['id'];
$text = $update['message']['text'] ?? '';
if (str_starts_with($text, '/start')) {
sendMessage($baseUrl, $chatId, "Hello! I'm a WWChat bot.");
} elseif (str_starts_with($text, '/help')) {
sendMessage($baseUrl, $chatId, "Commands:\n/start\n/help");
} else {
sendMessage($baseUrl, $chatId, "You wrote: $text");
}
}
} Limits
Rate Limits
| Limit | Value |
|---|---|
| Requests per second (all methods) | 30 per bot |
| Messages per second per chat | 1 per bot |
| Messages per minute per chat | 20 per bot |
When rate limit is exceeded, 429 Too Many Requests is returned with a Retry-After: 1 header.
Per-chat message limits apply to: sendMessage, sendDocument, sendPhoto, sendVoice, sendInvoice.
Inline Keyboard
| Limit | Value |
|---|---|
| Max rows | 25 |
| Max buttons per row | 8 |
| Max buttons total | 100 |
| Button text | 256 bytes |
| callback_data | 64 bytes |
Messages
| Limit | Value |
|---|---|
| Message text | 4096 characters |
| Invoice title | 255 characters |
| Invoice description | 1000 characters |
| Invoice payload | 512 characters |
Other
- Long polling timeout: max 60 seconds
- Updates limit: max 100 per request
- Webhook URL: HTTPS only
- Webhook max_connections: 1-100
- File upload: max 50 MB
- Max 20 bots per user (including deleted)
- Bot username is reserved forever (even after deletion)
System Bots
@BotMama
System bot for creating and managing bots:
/newbot— create a new bot/mybots— list my bots/editbot— edit bot (name, description, avatar)/deletebot— delete a bot/token— get a new token