Всё для подключения

Документация

Как получить задания, проверить выполнение и принимать события. С примерами, которые можно скопировать.

С чего начать

API нужно, если вы хотите выдавать задания в своём боте. Добавьте бота в Flowly, дождитесь модерации и скопируйте его API-ключ в разделе «Интеграция».

  1. Ваш бот отправляет ID пользователя и получает список заданий.
  2. Вы показываете пользователю кнопки со ссылками из ответа.
  3. Flowly проверяет выполнение. Вы можете запросить результат сами или получать вебхуки.
API-ключ Flowly и токен Telegram-бота — разные вещи. Для запросов ниже нужен ключ Flowly.
Адрес API и заголовок авторизации
https://flowly.guru/api/v1

Auth: YOUR_BOT_API_KEY

Получить задания

Передавайте настоящий Telegram ID пользователя, язык интерфейса и наличие Premium. Количество заданий не превышает лимит в настройках вашей площадки.

POST/api/v1/offers
Тело запроса
{"tg_user_id": 12344566, "lang": "ru", "is_premium": false, "offers_limit": 3}
Пример ответа
{
  "status": "not_ok",
  "offers": [{
    "offer_id": 7,
    "name": "Название канала",
    "price": "0.80",
    "link": "https://t.me/example",
    "type": "channel",
    "channel_id": "-1001234567890",
    "subscribed": false
  }],
  "description": "Subscription required"
}
not_ok

Есть задания. Покажите пользователю кнопки.

ok

Задания выполнены.

no_offers

Подходящих заданий сейчас нет.

name — название ресурса, price — цена задания в рублях, link — готовая ссылка. Не переделывайте её. subscribed показывает, выполнено ли задание. Тип: channel, bot или resource.

Для ботов без токена и ресурсов ссылки всегда ведут через Mini App. Для остальных заданий Mini App используется, если Flowly ещё не знает IP и данные устройства пользователя. При первом открытии Flowly сохранит данные и проверит условия задания. Если оно не подходит пользователю, сервис подберёт другое. Если подходящих заданий нет, Mini App сообщит об этом и закроется.

Проверить выполнение

POST/api/v1/check

Проверьте одно из выданных заданий. Повторная проверка не начисляет деньги повторно.

Тело запроса
{"tg_user_id": 12344566, "offer_id": 7}
Пример ответа
{"status": "subscribed"}

subscribed — выполнено. not_subscribed — ещё не выполнено. offer_unavailable — задание уже недоступно.

Для канала или чата заявка на вступление тоже считается выполнением. В течение трёх дней Flowly проверяет отписки: если пользователь уйдёт, начисление продавцу отменяется, а деньги возвращаются заказчику.

Боты без токена и внешние ресурсы всегда выдаются через Mini App Flowly. Для зачёта по предположению пользователь должен открыть именно это задание через выданную персональную ссылку. Проверка доступна через 5 секунд после открытия; выполнение других заданий не требуется. Переход не подтверждает действие внутри бота или ресурса.

Обновить задания

POST/api/v1/reset_offers

Сбрасывает текущий список для пользователя. После этого снова вызовите /offers.

Тело запроса
{"tg_user_id": 12344566}

Задания также обновляются автоматически через время, заданное в настройках площадки. Старые Mini App ссылки перестают работать. Прямая Telegram-ссылка может открыться и позже, но просроченное задание не оплачивается.

Баланс и статистика

GET/api/v1/balance
Пример ответа
{"balance": "120.00", "earned_by_bot": "48.00", "currency": "RUB"}

balance — баланс владельца, earned_by_bot — заработок этого бота.

GET/api/v1/statistics?date=2026-10-05
Пример ответа
{"date": "2026-10-05", "subs_count": 12, "income": "9.60", "currency": "RUB"}

Дата в формате год-месяц-день. Все дни считаются по московскому времени. Чтобы посмотреть графики и все площадки, войдите в кабинет →.

Вебхуки: подписки и отписки

Вебхук — это POST-запрос от Flowly на ваш сервер. Он приходит, когда сервис засчитывает подписку или замечает отписку. Постоянно запрашивать результат не нужно.

В карточке бота откройте «Вебхуки», выберите событие и укажите HTTPS-адрес обработчика. Подписки и отписки можно отправлять на разные адреса.

Тело вебхука
{
  "event_id": "уникальный-id-события",
  "tg_user_id": 12344566,
  "link": "https://t.me/example",
  "status": "subscribed",
  "reason": "subscribed",
  "occurred_at": "2026-10-05T12:00:00+03:00"
}

При отписке status и reason будут unsubscribed. Найденная заявка на вступление отправляет событие подписки так же, как обычное вступление.

Для чего ключ подписи?

Чтобы ваш сервер мог убедиться, что запрос пришёл от Flowly. Ключ берётся в настройках вебхуков и хранится на вашем сервере. Это отдельный секрет: не API-ключ и не токен Telegram.

Flowly передаёт подпись в заголовке X-Flowly-Signature. Сравнивайте её с HMAC-SHA256 от исходного тела запроса — до разбора JSON.

Проверка подписи на Python
import hashlib
import hmac
import os

body = await request.read()
secret = os.environ["FLOWLY_WEBHOOK_SECRET"].encode()
expected = "sha256=" + hmac.new(secret, body, hashlib.sha256).hexdigest()
received = request.headers.get("X-Flowly-Signature", "")
if not hmac.compare_digest(received, expected):
    raise PermissionError("Неверная подпись")

Успешно приняли событие — верните ответ 2xx. Если сервер недоступен, Flowly повторит отправку до 10 раз. Сохраняйте event_id и обрабатывайте каждый ID только один раз: одно событие может прийти повторно. ID также есть в заголовке X-Flowly-Event-ID.

Пример бота на Python

Пример показывает список заданий кнопками и общую кнопку проверки. После выполнения — сообщение о награде. Само начисление награды нужно добавить в вашу базу: здесь оно не реализовано.

Установка
pip install aiogram aiohttp
bot.py
import asyncio
import os
import aiohttp
from aiogram import Bot, Dispatcher, F
from aiogram.filters import CommandStart
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup

dp = Dispatcher()

async def offers(user):
    async with http.post(
        "https://flowly.guru/api/v1/offers",
        headers={"Auth": os.environ["FLOWLY_API_KEY"]},
        json={"tg_user_id": user.id,
              "lang": user.language_code or "ru",
              "is_premium": bool(user.is_premium)},
    ) as response:
        if response.status != 200:
            raise RuntimeError("Проверка временно недоступна")
        return await response.json()

def buttons(result):
    rows = [[InlineKeyboardButton(text=item["name"], url=item["link"])]
            for item in result.get("offers", [])
            if not item.get("subscribed")]
    rows.append([InlineKeyboardButton(text="Проверить выполнение", callback_data="check")])
    return InlineKeyboardMarkup(inline_keyboard=rows)

@dp.message(CommandStart())
async def start(message):
    try:
        result = await offers(message.from_user)
    except (RuntimeError, aiohttp.ClientError, asyncio.TimeoutError):
        await message.answer("Не получилось получить задания. Попробуйте чуть позже.")
        return
    if result["status"] == "ok":
        await message.answer("Задания уже выполнены!")
    elif result["status"] == "no_offers":
        await message.answer("Сейчас заданий нет. Загляните позже.")
    else:
        await message.answer("Выполните задания ниже, затем нажмите «Проверить выполнение».",
                             reply_markup=buttons(result))

@dp.callback_query(F.data == "check")
async def check(callback):
    await callback.answer()
    try:
        result = await offers(callback.from_user)
    except (RuntimeError, aiohttp.ClientError, asyncio.TimeoutError):
        await callback.message.answer("Не удалось проверить. Попробуйте ещё раз позже.")
        return
    if result["status"] == "ok":
        # Здесь начислите награду в своей БД только один раз.
        await callback.message.edit_text("Готово! Здесь пользователь получит свою награду 🎉")
    elif result["status"] == "no_offers":
        await callback.message.edit_text("Сейчас подходящих заданий нет.")
    else:
        await callback.message.answer("Остались невыполненные задания.", reply_markup=buttons(result))

async def main():
    global http
    async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=15)) as http:
        async with Bot(os.environ["BOT_TOKEN"]) as bot:
            await dp.start_polling(bot)

if __name__ == "__main__":
    asyncio.run(main())

Перед запуском задайте переменные окружения BOT_TOKEN и FLOWLY_API_KEY. Не вставляйте ключи в публичный код.

Если что-то пошло не так

КодЧто делать
400Проверьте поля запроса и формат даты.
401Проверьте ключ в заголовке Auth и статус бота.
404Задание не найдено или не выдавалось этому пользователю.
409Задание недоступно или не хватает средств.
503Telegram временно не ответил. Повторите запрос позже; не засчитывайте задание из-за ошибки.

Если разобраться не получается, напишите в поддержку ↗. Укажите метод, код ответа и время запроса. Ключи присылать не нужно.