PRocket API
Монетизация Telegram-ботов: обязательная подписка, задания и показы. Подключение занимает пять строк — текст, клавиатуру и кнопку «Проверить» отдаёт сервер уже собранными.
Обязательная подписка
Пользователь не пользуется ботом, пока не подпишется на спонсоров.
Задания
Пользователь получает награду за каждое выполненное задание.
Показы и приветы
Сервер сам отправляет рекламный пост через токен вашего бота.
Шаг 1. Получите ключ
Откройте мини-апп PRocket → раздел Интеграция → Подключить бота.
После подключения вы увидите ключ вида prk_live_… — он даёт право
действовать от имени бота.
Ни в одном запросе нет поля, которое его принимает. Токен нужен только при подключении бота в мини-аппе — там он и остаётся, зашифрованным.
Шаг 2. Установите библиотеку
pip install procketapinpm install procketapi# Библиотека не нужна — API это обычный POST с JSONШаг 3. Три строки в хендлер
from aiogram import Bot, Dispatcher, types
from procketapi import PRocket
bot = Bot("ТОКЕН_БОТА")
dp = Dispatcher()
procket = PRocket("ВАШ_КЛЮЧ")
@dp.message()
async def handler(message: types.Message):
# Не подписан — клиент сам отправит спонсоров и вернёт False
if not await procket.check(message.from_user.id, bot=bot):
return
await message.answer("Доступ открыт")import { Bot } from "grammy";
import { PRocket } from "procketapi";
const bot = new Bot("ТОКЕН_БОТА");
const procket = new PRocket("ВАШ_КЛЮЧ");
bot.on("message", async (ctx, next) => {
// Не подписан — клиент сам отправит спонсоров
const result = await procket.check(ctx.from.id, { bot });
if (!result.passed) return;
await next();
});
bot.command("start", (ctx) => ctx.reply("Доступ открыт"));
bot.start();curl -X POST https://app.procket.club/api/v2/check \
-H "Content-Type: application/json" \
-H "Auth: ВАШ_КЛЮЧ" \
-d '{"user_id": 123456789, "language_code": "ru"}'Дальше — только если нужны задания, показы или своё оформление.
Аутентификация
Все запросы — POST с телом JSON. Ключ можно передать
любым из способов ниже: они равнозначны, выбирайте удобный.
Auth: ВАШ_КЛЮЧ
Authorization: Bearer ВАШ_КЛЮЧ
Authorization: ВАШ_КЛЮЧ
X-Procket-Key: ВАШ_КЛЮЧ
X-Api-Key: ВАШ_КЛЮЧ{
"key": "ВАШ_КЛЮЧ",
"user_id": 123456789
}Чтобы код, написанный по документации другого сервиса, заработал после
замены одного домена. Разбор ключа разрешающий, проверка — нет: неверный
ключ всё равно даёт 401.
Базовый адрес
Каждый метод доступен по трём путям сразу: /api/v2/check,
/api/check и просто /check. Выбирайте любой.
Обязательная подписка
Пользователь не попадает в бота, пока не подпишется на выданных спонсоров. Владелец получает оплату за каждую подписку.
Запрос
| Поле | Тип | Описание | |
|---|---|---|---|
user_id | integer | обяз. | Telegram ID пользователя. Принимаются также chat_id и tg_user_id. |
limit | integer | — | Сколько спонсоров выдать. 1–5, по умолчанию 3. |
language_code | string | — | Язык пользователя. Определяет язык готового сообщения. |
is_premium | boolean | — | Telegram Premium. Влияет на подбор кампаний. |
message | object | — | Своё оформление, см. ниже. |
Ответ
{
"passed": false,
"skip": false,
"status": "not_ok",
"reason": "pending",
"description": "sponsors pending subscription",
"offers": [
{
"id": "cmp_a1b2",
"ticket": "atc_x9y8",
"type": "channel",
"title": "Канал о крипте",
"url": "https://t.me/example",
"photo": null,
"button": "Перейти в канал",
"reward": 1.5,
"currency": "USD",
"done": false,
"state": "open"
}
],
"message": {
"text": "Для доступа к боту подпишитесь на спонсоров:",
"parse_mode": "HTML",
"reply_markup": {
"inline_keyboard": [
[{ "text": "Подписаться", "url": "https://t.me/example" }],
[{ "text": "Проверить ✅", "callback_data": "procket_check" }]
]
}
},
"attached_until": "2026-08-05T13:00:00.000Z"
}| Поле | Описание |
|---|---|
passed | Главное поле. true — пускать дальше, false — показать message. |
skip / status | То же самое под именами из других сервисов. Всегда согласованы с passed. |
reason | pending, completed (подписан на всё) или no_offers (нет подходящих кампаний). |
offers | Спонсоры. Собирать клавиатуру вручную нужно, только если не устраивает message. |
message | Готовое сообщение. Отправляется в Telegram как есть. null, когда passed: true. |
attached_until | До какого момента спонсоры закреплены за пользователем. |
check и есть проверка.
Именно его вешают на кнопку «Проверить». Сервер сам перепроверит членство
по уже выданным спонсорам и вернёт passed: true, когда всё выполнено.
Привязка живёт час (attached_until). Это не ошибка: пользователь
их уже видит. Не вызывайте check в цикле ради «обновления выдачи» —
так вы завысите показы и занизите конверсию, и заметно это станет через недели,
по неправильным цифрам.
Полный пример
import asyncio
from aiogram import Bot, Dispatcher, F, types
from aiogram.filters import Command
from procketapi import PRocket
bot = Bot("ТОКЕН_БОТА")
dp = Dispatcher()
procket = PRocket("ВАШ_КЛЮЧ")
@dp.message(Command("start"))
async def start(message: types.Message):
if not await procket.check(
message.from_user.id,
bot=bot,
language_code=message.from_user.language_code,
is_premium=message.from_user.is_premium,
):
return
await message.answer("Доступ открыт")
@dp.callback_query(F.data == "procket_check")
async def on_check(callback: types.CallbackQuery):
await callback.answer()
# Повторный вызов и есть проверка
if await procket.check(callback.from_user.id, bot=bot):
await callback.message.answer("Спасибо! Доступ открыт.")
else:
await callback.message.answer("Вы подписались не на всех спонсоров.")
asyncio.run(dp.start_polling(bot))import TelegramBot from "node-telegram-bot-api";
import { PRocket } from "procketapi";
const bot = new TelegramBot("ТОКЕН_БОТА", { polling: true });
const procket = new PRocket("ВАШ_КЛЮЧ");
bot.onText(/\/start/, async (msg) => {
const result = await procket.check(msg.from.id, {
bot,
languageCode: msg.from.language_code,
isPremium: msg.from.is_premium
});
if (!result.passed) return; // спонсоры уже отправлены
await bot.sendMessage(msg.chat.id, "Доступ открыт");
});
bot.on("callback_query", async (query) => {
if (query.data !== "procket_check") return;
await bot.answerCallbackQuery(query.id);
// Повторный вызов и есть проверка
const result = await procket.check(query.from.id, { bot });
await bot.sendMessage(
query.message.chat.id,
result.passed ? "Спасибо! Доступ открыт." : "Вы подписались не на всех спонсоров."
);
});<?php
function procketCheck(int $userId, ?string $lang = null): array
{
$ch = curl_init('https://app.procket.club/api/v2/check');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Auth: ' . PROCKET_KEY,
],
CURLOPT_POSTFIELDS => json_encode([
'user_id' => $userId,
'language_code' => $lang,
]),
]);
$response = curl_exec($ch);
curl_close($ch);
// Сервис недоступен — пропускаем: бот важнее офферов
if ($response === false) {
return ['passed' => true];
}
return json_decode($response, true) ?: ['passed' => true];
}
$result = procketCheck($userId, 'ru');
if (!$result['passed']) {
// message отправляется в Telegram как есть
sendTelegram('sendMessage', array_merge(
['chat_id' => $userId],
$result['message']
));
return;
}package procket
import (
"bytes"
"encoding/json"
"net/http"
"time"
)
type Offer struct {
ID string `json:"id"`
Ticket string `json:"ticket"`
Title string `json:"title"`
URL string `json:"url"`
Reward float64 `json:"reward"`
Currency string `json:"currency"`
}
type Message struct {
Text string `json:"text"`
ParseMode string `json:"parse_mode"`
ReplyMarkup json.RawMessage `json:"reply_markup"`
}
type CheckResult struct {
Passed bool `json:"passed"`
Reason string `json:"reason"`
Offers []Offer `json:"offers"`
Message *Message `json:"message"`
}
var client = &http.Client{Timeout: 10 * time.Second}
func Check(key string, userID int64, lang string) (*CheckResult, error) {
body, _ := json.Marshal(map[string]any{
"user_id": userID,
"language_code": lang,
})
req, err := http.NewRequest("POST", "https://app.procket.club/api/v2/check", bytes.NewReader(body))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Auth", key)
resp, err := client.Do(req)
if err != nil {
// Сервис недоступен — пропускаем: бот важнее офферов
return &CheckResult{Passed: true, Reason: "unavailable"}, nil
}
defer resp.Body.Close()
var result CheckResult
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return nil, err
}
return &result, nil
}using System.Net.Http.Json;
using System.Text.Json.Serialization;
public record Offer(
[property: JsonPropertyName("id")] string Id,
[property: JsonPropertyName("ticket")] string Ticket,
[property: JsonPropertyName("title")] string Title,
[property: JsonPropertyName("url")] string Url,
[property: JsonPropertyName("reward")] decimal Reward);
public record CheckResult(
[property: JsonPropertyName("passed")] bool Passed,
[property: JsonPropertyName("reason")] string Reason,
[property: JsonPropertyName("offers")] Offer[] Offers);
public sealed class PRocketClient
{
private static readonly HttpClient Http = new()
{
BaseAddress = new Uri("https://app.procket.club"),
Timeout = TimeSpan.FromSeconds(10)
};
private readonly string _key;
public PRocketClient(string key) => _key = key;
public async Task<CheckResult> CheckAsync(long userId, string? lang = null)
{
using var request = new HttpRequestMessage(HttpMethod.Post, "/api/v2/check")
{
Content = JsonContent.Create(new { user_id = userId, language_code = lang })
};
request.Headers.Add("Auth", _key);
try
{
var response = await Http.SendAsync(request);
return await response.Content.ReadFromJsonAsync<CheckResult>()
?? new CheckResult(true, "unavailable", []);
}
catch (HttpRequestException)
{
// Сервис недоступен — пропускаем: бот важнее офферов
return new CheckResult(true, "unavailable", []);
}
}
}curl -X POST https://app.procket.club/api/v2/check \
-H "Content-Type: application/json" \
-H "Auth: ВАШ_КЛЮЧ" \
-d '{
"user_id": 123456789,
"limit": 3,
"language_code": "ru",
"is_premium": false
}'Задания
То же самое, но пользователь получает награду, а не доступ. Начисляете её вы — сервер лишь подтверждает, что задание выполнено.
Запрос
| Поле | Тип | Описание | |
|---|---|---|---|
user_id | integer | обяз. | Telegram ID пользователя. |
limit | integer | — | Сколько заданий выдать. 1–10, по умолчанию 5. |
language_code | string | — | Язык пользователя. |
is_premium | boolean | — | Telegram Premium. |
Ответ
{
"tasks": [
{
"id": "cmp_a1b2",
"ticket": "atc_x9y8",
"type": "channel",
"title": "Канал о крипте",
"url": "https://t.me/example",
"button": "Перейти в канал",
"reward": 1.5,
"currency": "USD",
"done": false,
"state": "open"
}
],
"completed": false,
"skip": false,
"message": { "text": "…", "parse_mode": "HTML", "reply_markup": { } },
"attached_until": "2026-08-05T13:00:00.000Z"
}Проверка одного задания
Передаётся ticket из задания — идентификатор привязки к
пользователю, не id кампании.
{
"state": "done",
"result": "done",
"completed": true,
"reward": 1.5,
"currency": "USD"
}state | Значение |
|---|---|
open | выдано, ещё не выполнено |
done | выполнено — начисляйте награду |
waiting | выполнено, идёт удержание |
paid | оплачено владельцу |
expired | привязка истекла |
cancelled | снято (кампания недоступна для проверки) |
reverted | пользователь отписался, начисление отменено |
Один и тот же ticket будет возвращать done до
истечения привязки. Без своей проверки награда начислится столько раз,
сколько пользователь нажмёт кнопку.
Полный пример
from aiogram import F, types
from aiogram.filters import Command
from procketapi import PRocket
procket = PRocket("ВАШ_КЛЮЧ")
@dp.message(Command("tasks"))
async def show_tasks(message: types.Message):
result = await procket.get_tasks(message.from_user.id, limit=5)
if not result:
await message.answer("Сейчас заданий нет. Загляните позже.")
return
# Готовое сообщение — клавиатуру верстать не нужно
await message.answer(**result.message.as_kwargs())
@dp.callback_query(F.data == "procket_check")
async def check_tasks(callback: types.CallbackQuery):
await callback.answer()
result = await procket.get_tasks(callback.from_user.id)
earned = 0.0
for task in result:
state = await procket.check_task(task.ticket)
# already_paid — ваша таблица; без неё награда начислится дважды
if state and not await already_paid(task.ticket):
await mark_paid(task.ticket)
earned += state.reward
if earned:
await callback.message.answer(f"Начислено: {earned:.2f}")
else:
await callback.message.answer("Задания ещё не выполнены.")import { PRocket } from "procketapi";
const procket = new PRocket("ВАШ_КЛЮЧ");
bot.command("tasks", async (ctx) => {
const { tasks, message } = await procket.getTasks(ctx.from.id, { limit: 5 });
if (!tasks.length) {
return ctx.reply("Сейчас заданий нет. Загляните позже.");
}
// Готовое сообщение — клавиатуру верстать не нужно
await ctx.reply(message.text, {
parse_mode: message.parse_mode,
reply_markup: message.reply_markup
});
});
bot.callbackQuery("procket_check", async (ctx) => {
await ctx.answerCallbackQuery();
const { tasks } = await procket.getTasks(ctx.from.id);
let earned = 0;
for (const task of tasks) {
const state = await procket.checkTask(task.ticket);
// alreadyPaid — ваша таблица; без неё награда начислится дважды
if (state.completed && !(await alreadyPaid(task.ticket))) {
await markPaid(task.ticket);
earned += state.reward;
}
}
await ctx.reply(earned ? `Начислено: ${earned.toFixed(2)}` : "Задания ещё не выполнены.");
});# Выдать задания
curl -X POST https://app.procket.club/api/v2/tasks \
-H "Content-Type: application/json" \
-H "Auth: ВАШ_КЛЮЧ" \
-d '{"user_id": 123456789, "limit": 5}'
# Проверить одно задание
curl -X POST https://app.procket.club/api/v2/check_task \
-H "Content-Type: application/json" \
-H "Auth: ВАШ_КЛЮЧ" \
-d '{"ticket": "atc_x9y8"}'Показы и приветы
Рекламный пост в личку пользователю. Отправляет его сервер через токен вашего бота — вам не нужно ничего верстать и пересылать.
Запрос
| Поле | Тип | Описание | |
|---|---|---|---|
user_id | integer | обяз. | Кому отправить. Принимается также SendToChatId. |
hi | boolean | — | Режим привета: только для новых пользователей, не чаще раза в сутки. |
language_code | string | — | Язык пользователя. |
Коды результата
| Код | Значение | Что делать |
|---|---|---|
1 | Пост доставлен | ничего |
2 | Токен бота недействителен | переподключить бота в мини-аппе |
3 | Пользователь заблокировал бота | пометить у себя, больше не слать |
4 | Превышен лимит | повторить позже |
5 | Ошибка Telegram | повторить позже |
6 | Внутренняя ошибка | повторить позже |
7 | Лимит показов исчерпан | штатно, ничего |
8 | Нет подходящей рекламы | штатно, ничего |
9 | Бот выключен в настройках | включить в мини-аппе |
10 | Бот заблокирован | обратиться в поддержку |
11 | Бот на модерации | дождаться проверки |
hi: true — только сразу после /start нового
пользователя и не чаще раза в сутки на человека.
Обычные показы — только после осмысленного действия пользователя, а не на каждое сообщение и не по таймеру.
Не вызывайте метод без входящего апдейта и не подставляйте чужие
user_id.
Пример
from aiogram.filters import Command
from procketapi import AdResult, PRocket
procket = PRocket("ВАШ_КЛЮЧ")
@dp.message(Command("start"))
async def start(message: types.Message):
# Привет: только для новых пользователей, первым делом после /start
if await is_new_user(message.from_user.id):
await procket.send_ad(message.from_user.id, hi=True)
await message.answer("Привет! Я умею…")
@dp.message(Command("profile"))
async def profile(message: types.Message):
await message.answer(render_profile(message.from_user.id))
# Обычный показ — после осмысленного действия, а не на /start
result = await procket.send_ad(message.from_user.id)
if result.code == AdResult.USER_FORBIDDEN:
await mark_blocked(message.from_user.id)import { AdResult, PRocket } from "procketapi";
const procket = new PRocket("ВАШ_КЛЮЧ");
bot.command("start", async (ctx) => {
// Привет: только для новых пользователей, первым делом после /start
if (await isNewUser(ctx.from.id)) {
await procket.sendAd(ctx.from.id, { hi: true });
}
await ctx.reply("Привет! Я умею…");
});
bot.command("profile", async (ctx) => {
await ctx.reply(renderProfile(ctx.from.id));
// Обычный показ — после осмысленного действия, а не на /start
const result = await procket.sendAd(ctx.from.id);
if (result.code === AdResult.USER_FORBIDDEN) {
await markBlocked(ctx.from.id);
}
});curl -X POST https://app.procket.club/api/v2/ad/send \
-H "Content-Type: application/json" \
-H "Auth: ВАШ_КЛЮЧ" \
-d '{"user_id": 123456789, "hi": true}'
# {"SendPostResult": 1, "result": 1, "ok": true,
# "description": "Advertisement delivered"}Вебхуки
Сервер сообщит, когда пользователь выполнил подписку или задание — не нужно опрашивать API в цикле. Адрес задаётся в карточке интеграции.
Тело события
{
"id": "evt_7f3a",
"type": "task.completed",
"created_at": "2026-08-05T12:00:00.000Z",
"bot_id": "bot_a1b2",
"data": {
"ticket": "atc_x9y8",
"user_id": 123456789,
"reward": 1.5
}
}| Событие | Когда |
|---|---|
subscription.completed | пользователь подписался на спонсора в режиме ОП |
task.completed | пользователь выполнил задание |
Заголовки
| Заголовок | Значение |
|---|---|
X-Procket-Event | тип события |
X-Procket-Delivery | идентификатор доставки |
X-Procket-Signature | t=<unix>,v1=<hex> |
Проверка подписи
Подпись — HMAC-SHA256 по строке <t>.<тело> секретом вебхука.
Без этого любой, кто узнал адрес вашего обработчика, пришлёт поддельное «задание выполнено» — и вы начислите за него награду.
Считайте подпись по исходным байтам тела. Разбор JSON с обратной сборкой её ломает: порядок ключей не сохраняется.
Запросы старше 5 минут отвергайте, иначе перехваченный запрос переигрывается.
import json
from aiohttp import web
from procketapi import WebhookSignatureError, verify_webhook
SECRET = "СЕКРЕТ_ИЗ_КАРТОЧКИ_ИНТЕГРАЦИИ"
async def handle(request: web.Request):
# Именно байты, не разобранный JSON
body = await request.read()
try:
verify_webhook(body, request.headers.get("X-Procket-Signature"), SECRET)
except WebhookSignatureError:
return web.Response(status=403)
event = json.loads(body)
data = event["data"]
if event["type"] == "task.completed":
# Повтор доставки — штатная ситуация, проверьте ticket у себя
if not await already_paid(data["ticket"]):
await give_reward(data["user_id"], data["reward"])
await mark_paid(data["ticket"])
# 2xx = доставлено
return web.json_response({"ok": True})
app = web.Application()
app.router.add_post("/procket/webhook", handle)
web.run_app(app, port=8080)import express from "express";
import { verifyWebhook, WebhookSignatureError } from "procketapi";
const app = express();
// express.raw, не express.json: подпись считается по исходным байтам
app.post(
"/procket/webhook",
express.raw({ type: "application/json" }),
async (req, res) => {
try {
verifyWebhook(req.body, req.get("X-Procket-Signature"), process.env.PROCKET_SECRET);
} catch (error) {
if (error instanceof WebhookSignatureError) return res.sendStatus(403);
throw error;
}
const event = JSON.parse(req.body.toString("utf8"));
const data = event.data;
if (event.type === "task.completed") {
// Повтор доставки — штатная ситуация, проверьте ticket у себя
if (!(await alreadyPaid(data.ticket))) {
await giveReward(data.user_id, data.reward);
await markPaid(data.ticket);
}
}
res.json({ ok: true }); // 2xx = доставлено
}
);
app.listen(8080);<?php
$secret = getenv('PROCKET_SECRET');
$body = file_get_contents('php://input'); // исходные байты
$header = $_SERVER['HTTP_X_PROCKET_SIGNATURE'] ?? '';
parse_str(strtr($header, ',', '&'), $parts);
$timestamp = $parts['t'] ?? '';
$provided = $parts['v1'] ?? '';
// Запросы старше 5 минут отвергаем: иначе перехваченный переигрывается
if (!$timestamp || abs(time() - (int) $timestamp) > 300) {
http_response_code(403);
exit;
}
$expected = hash_hmac('sha256', $timestamp . '.' . $body, $secret);
// hash_equals, а не ===: сравнение строк выдаёт подпись по времени ответа
if (!hash_equals($expected, $provided)) {
http_response_code(403);
exit;
}
$event = json_decode($body, true);
if ($event['type'] === 'task.completed') {
$data = $event['data'];
// Повтор доставки — штатная ситуация, проверьте ticket у себя
if (!alreadyPaid($data['ticket'])) {
giveReward($data['user_id'], $data['reward']);
markPaid($data['ticket']);
}
}
header('Content-Type: application/json');
echo json_encode(['ok' => true]); // 2xx = доставленоПовторы
Ответ 2xx считается доставкой. Иначе повторы через
10 с, 60 с, 5 мин, 30 мин и 2 ч, после чего доставка
признаётся неудавшейся. Пять подряд неудавшихся доставок отключают вебхук.
Библиотеки
Официальные клиенты берут на себя ретраи, таймауты и отправку сообщений.
Python
pip install procketapi
aiogram, telebot, aiohttp
Node.js
npm install procketapi
grammY, Telegraf, node-telegram-bot-api
Middleware: подписка на весь бот одной строкой
from procketapi import PRocket, PRocketMiddleware
procket = PRocket("ВАШ_КЛЮЧ")
# skip_commands — то, что должно работать без подписки
dp.message.middleware(PRocketMiddleware(procket, skip_commands=("/help", "/support")))
dp.callback_query.middleware(PRocketMiddleware(procket))
# Ни один хендлер ниже не выполнится, пока пользователь не подписан.
# Нажатия на кнопку «Проверить» обрабатываются внутри middleware.import { PRocket } from "procketapi";
const procket = new PRocket("ВАШ_КЛЮЧ");
bot.use(async (ctx, next) => {
if (!ctx.from) return next();
// /help должен работать без подписки
if (ctx.message?.text?.startsWith("/help")) return next();
const result = await procket.check(ctx.from.id, {
bot,
languageCode: ctx.from.language_code
});
if (!result.passed) return;
return next();
});Поведение при сбоях
Если PRocket недоступен, check() возвращает «пропустить»:
пользователь продолжает пользоваться ботом, а владелец теряет один показ
вместо всех сразу. Офферы важнее нуля, но бот важнее офферов.
Строгое поведение включается через raise_on_error=True
(Python) или throwOnError: true (JS).
Своё оформление
По умолчанию сервер собирает сообщение на языке пользователя. Поле
message в запросе переопределяет текст и подписи кнопок.
| Поле | Тип | Описание |
|---|---|---|
text | string | Текст сообщения. HTML-разметка Telegram. |
rows | integer | Кнопок в ряд, 1–4. По умолчанию 1. |
button_channel | string | Подпись кнопки канала. |
button_bot | string | Подпись кнопки бота. |
button_url | string | Подпись кнопки ссылки. |
button_check | string | Подпись кнопки «Проверить». |
check_callback | string | callback_data кнопки проверки. По умолчанию procket_check. |
Подстановки в text
| Подстановка | Значение |
|---|---|
$name / {name} | имя пользователя из user_name |
{count} | сколько спонсоров выдано |
{reward} | суммарная награда |
{currency} | валюта награды |
await procket.check(
user_id,
bot=bot,
user_name=message.from_user.first_name,
message={
"rows": 2,
"text": "<b>$name</b>, подпишитесь на {count} канала — и бот ваш:",
"button_channel": "Подписаться",
"button_check": "Готово ✅",
},
)await procket.check(userId, {
bot,
userName: ctx.from.first_name,
message: {
rows: 2,
text: "<b>$name</b>, подпишитесь на {count} канала — и бот ваш:",
button_channel: "Подписаться",
button_check: "Готово ✅"
}
});curl -X POST https://app.procket.club/api/v2/check \
-H "Content-Type: application/json" \
-H "Auth: ВАШ_КЛЮЧ" \
-d '{
"user_id": 123456789,
"user_name": "Иван",
"message": {
"rows": 2,
"text": "$name, подпишитесь на {count} канала:",
"button_channel": "Подписаться",
"button_check": "Готово ✅"
}
}'Справочник API
Все методы — POST с телом JSON. Каждый доступен по любому из псевдонимов.
| Метод | Псевдонимы | Назначение |
|---|---|---|
/api/v2/check | /check, /subscription, /offers, /sub | Обязательная подписка |
/api/v2/tasks | /tasks, /get_tasks | Выдать задания |
/api/v2/check_task | /check_task, /tasks/check, /task_status | Состояние задания |
/api/v2/ad/send | /ad/send, /ad/SendPost, /views, /hi | Показ или привет |
/api/v2/reset | /reset, /reset_offers | Закрыть привязки пользователя |
/api/v2/me | /me, /get_me, /info | Бот, баланс, вебхук, лимиты |
Объект оффера
| Поле | Тип | Описание |
|---|---|---|
id | string | Идентификатор кампании. |
ticket | string | Привязка оффера к пользователю. Именно он идёт в check_task, не id. |
type | string | channel, bot, group или link. |
title | string | Название ресурса. |
url | string | Ссылка для кнопки. |
photo | string | null | Аватар ресурса, если есть. |
button | string | Рекомендованная подпись кнопки. |
reward | number | Ваша выплата за выполнение. |
currency | string | Валюта выплаты. |
done | boolean | Выполнен ли оффер. |
state | string | См. таблицу состояний в разделе «Задания». |
/api/v2/me
{
"bot": {
"id": "bot_a1b2",
"username": "example_bot",
"title": "Мой бот",
"status": "active"
},
"status": true,
"balance": 12.5,
"currency": "USD",
"webhook": { "url": "https://…", "events": [], "state": "active" },
"limits": { "attachment_ttl_seconds": 3600, "tasks_max_limit": 10 }
}webhook равен null, пока адрес не задан. Секреты не возвращаются никогда.
Ошибки и лимиты
| Код | Причина | Что делать клиенту |
|---|---|---|
200 | — | обработать тело |
401 | ключ не принят | остановиться навсегда, одна строка в лог |
404 | неизвестный тикет или опечатка в адресе | проверить ticket и путь |
422 | некорректный user_id | дефект клиента, исправить |
429 | превышен лимит | подождать Retry-After секунд |
5xx | сбой на нашей стороне | до 3 попыток, пауза 1 с с удвоением |
401 нужно останавливаться, а не повторять.
Ретраи не сделают ключ валидным, а лог владельца засорят. Официальные
библиотеки делают это сами: после первого 401 клиент замолкает
до перезапуска.
Лимиты
| Лимит | Значение |
|---|---|
| Запросов в минуту на бота | 60 |
| Время жизни привязки | 3600 с (1 час) |
| Спонсоров за раз (ОП) | 1–5, по умолчанию 3 |
| Заданий за раз | 1–10, по умолчанию 5 |
| Приветов на пользователя | 1 в сутки |
Тело ошибки
{
"error": "sdk_key_invalid",
"message": "Key not recognised. Check that you copied the whole key from Integration in the Mini App.",
"docs": "https://procketapi.best"
}Переход с других сервисов
API принимает поля и пути, принятые у других сервисов, поэтому в большинстве случаев меняется только домен и ключ.
| Было | Стало | Комментарий |
|---|---|---|
Auth: KEY | работает как есть | заголовок принимается |
Authorization: KEY | работает как есть | без Bearer тоже |
{"key": "…"} в теле | работает как есть | ключ в теле принимается |
chat_id, tg_user_id, SendToChatId | user_id | все три имени принимаются |
lang, user_locale | language_code | принимаются |
signature | ticket | принимается |
POST /get_tasks | POST /get_tasks | путь совпадает |
POST /check | POST /check | путь совпадает |
POST /ad/SendPost | POST /ad/send | старое написание принимается |
Что поменялось по смыслу
skipиstatusсохранены, но главное поле —passed.- Вместо массива ссылок приходит
offersс объектами и готовыйmessage. Строить клавиатуру вручную больше не нужно. - Проверка выполнения — это повторный
check, отдельного метода нет. - В
check_taskпередаётсяticket(привязка), а не идентификатор кампании.
Замените адрес на https://app.procket.club, подставьте свой ключ —
и проверьте, что читаете passed. Остальное совпадёт.