Bot API

RU EN

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:

  1. Open a chat with @BotMama
  2. Send the command /newbot
  3. Enter the bot name
  4. Enter username (must end with bot)
  5. 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

ParameterTypeRequiredDescription
chat_idStringYesChat UUID or @username
textStringYesMessage text
parse_modeStringNoFormatting: HTML or Markdown
reply_to_message_idStringNoMessage UUID to reply to
disable_notificationBooleanNoDisable notification
reply_markupInlineKeyboardMarkupNoInline 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

ParameterTypeDescription
offsetIntegerID of the first update to return
limitIntegerMax number (1-100)
timeoutIntegerLong polling timeout (max 60 sec)
allowed_updatesArrayFilter 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

ParameterTypeRequiredDescription
chat_idStringYesChat UUID
message_idStringYesMessage UUID
textStringYesNew text
reply_markupInlineKeyboardMarkupNoNew keyboard

Important: Only messages sent by the bot can be edited.


answerCallbackQuery

Respond to inline button press.

Request: POST /bot/v1/{token}/answerCallbackQuery

ParameterTypeDescription
callback_query_idStringCallback query ID (required)
textStringNotification text (up to 200 chars)
show_alertBooleanShow alert instead of toast
cache_timeIntegerCache 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

ParameterTypeRequiredDescription
chat_idStringYesChat 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)

ParameterTypeDescription
fileFileFile 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

ParameterTypeRequiredDescription
chat_idStringYesChat UUID
documentStringYesfile_id of uploaded file
captionStringNoDocument caption
parse_modeStringNoCaption formatting
reply_markupInlineKeyboardMarkupNoInline keyboard

sendPhoto

Send a photo.

Request: POST /bot/v1/{token}/sendPhoto

ParameterTypeRequiredDescription
chat_idStringYesChat UUID
photoStringYesfile_id of uploaded image
captionStringNoPhoto caption
parse_modeStringNoCaption formatting
reply_markupInlineKeyboardMarkupNoInline keyboard

sendVoice

Send a voice message.

Request: POST /bot/v1/{token}/sendVoice

ParameterTypeRequiredDescription
chat_idStringYesChat UUID
voiceStringYesfile_id of audio file
captionStringNoCaption
durationIntegerNoDuration in seconds
reply_markupInlineKeyboardMarkupNoInline keyboard

Star Payments

Bots can accept payments in stars.

Payment Flow

  1. Bot calls sendInvoice — user sees a message with "Pay" button
  2. User clicks "Pay" — client shows a modal with details
  3. User confirms — server sends pre_checkout_query update to the bot
  4. Bot calls answerPreCheckoutQuery (ok: true/false) — within 10 seconds
  5. If ok=true — stars are deducted from user and credited to bot
  6. Bot receives update with successful_payment in message

sendInvoice

Send an invoice for star payment.

Request: POST /bot/v1/{token}/sendInvoice

ParameterTypeRequiredDescription
chat_idStringYesChat UUID or @username
titleStringYesProduct/service name (up to 255 chars)
descriptionStringYesDescription (up to 1000 chars)
amountFloatYesAmount in stars (> 0)
photo_urlStringNoProduct image URL
payloadStringYesBot 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

ParameterTypeRequiredDescription
pre_checkout_query_idStringYesUUID from pre_checkout_query update
okBooleanYestrue — confirm, false — decline
error_messageStringNoError 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

ParameterTypeDescription
urlStringHTTPS webhook URL (required)
secret_tokenStringSecret for X-WWChat-Bot-Api-Secret-Token header
max_connectionsIntegerMax connections (1-100)
allowed_updatesArrayFilter 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

FieldTypeDescription
textStringButton text (required)
callback_dataStringCallback data (up to 64 bytes)
urlStringURL to open
payBooleantrue for payment button (invoice only)

User

FieldTypeDescription
idStringUser UUID
usernameStringUsername
is_botBooleantrue if bot
is_deletedBooleantrue if deleted

Chat

FieldTypeDescription
idStringChat UUID
typeStringprivate, group, channel
titleStringTitle (for groups/channels)
usernameStringGroup/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


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

LimitValue
Requests per second (all methods)30 per bot
Messages per second per chat1 per bot
Messages per minute per chat20 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

LimitValue
Max rows25
Max buttons per row8
Max buttons total100
Button text256 bytes
callback_data64 bytes

Messages

LimitValue
Message text4096 characters
Invoice title255 characters
Invoice description1000 characters
Invoice payload512 characters

Other

System Bots

@BotMama

System bot for creating and managing bots: