$ sudo teach IT
Модуль 6 · Polling, Webhook & Filters

Урок 6.3 — Webhook обработка

Научимся настраивать вебхуки в aiosend через AiohttpManager, FastAPIManager и FlaskManager, интегрировать с веб-фреймворками и использовать WebhookRouter для гибкой обработки.

Вебхуки (Webhook) — это механизм, при котором Crypto Pay API сам отправляет HTTP-запрос на ваш сервер при наступлении события (оплата инвойса, активация чека). В отличие от polling, вам не нужно постоянно опрашивать API — сервер сам уведомляет вас. aiosend поддерживает три веб-фреймворка из коробки: aiohttp, FastAPI и Flask.

🌐

Что такое Webhook и зачем он нужен

Вебхук — это обратный вызов (callback) от Crypto Pay API на ваш сервер. Когда происходит событие (например, оплата инвойса), Crypto Pay отправляет POST-запрос на заранее указанный URL вашего сервера с данными о событии.

Преимущества вебхуков перед polling:

✅ Вебхуки

  • Мгновенная реакция на события
  • Меньше запросов к API
  • Не блокирует приложение
  • Масштабируется лучше
  • Не требует постоянного соединения

⚠️ Polling

  • Задержка до delay секунд
  • Много холостых запросов
  • Нагрузка на API
  • Требует постоянного цикла
  • Проще в настройке (не нужен сервер)

Когда использовать вебхуки? Если у вас есть публичный сервер с HTTPS и вы хотите получать уведомления об оплате мгновенно. Если сервера нет (простой скрипт, бот) — используйте polling.

🔧

Настройка вебхуков в @CryptoBot

Прежде чем использовать вебхуки в коде, их нужно включить в настройках вашего приложения в @CryptoBot:

📋 Пошаговая инструкция

  1. Напишите @CryptoBot или @CryptoTestnetBot.
  2. Откройте раздел Crypto Pay → My Apps.
  3. Выберите ваше приложение.
  4. Нажмите Edit → Webhook.
  5. Включите вебхук (toggle ON).
  6. Укажите URL вашего сервера: https://your-domain.com/webhook.
  7. Нажмите Save.

Важные требования к вебхуку:

  • URL должен начинаться с https:// (Crypto Pay не отправляет на HTTP)
  • Сервер должен быть доступен из интернета
  • Время ответа сервера не должно превышать 5 секунд (иначе Crypto Pay считает, что доставка не удалась)
  • Crypto Pay будет повторять отправку при ошибках (до 24 часов)

В коде aiosend вы также можете программно установить/удалить вебхук через API:

Python · Установка вебхука через API
from aiosend import CryptoPay

cp = CryptoPay(token="YOUR_TOKEN")

# Установить вебхук
result = await cp.set_webhook(url="https://example.com/webhook")
print(f"Вебхук установлен: {result}")

# Удалить вебхук (для переключения на polling)
result = await cp.delete_webhook()
print(f"Вебхук удалён: {result}")

# Получить информацию о текущем вебхуке
info = await cp.get_webhook_info()
print(f"Текущий вебхук: {info.url if info else 'не установлен'}")
📦

WebhookManager — общий интерфейс

WebhookManager — это абстрактный базовый класс для всех менеджеров вебхуков. Он определяет интерфейс, который должны реализовать конкретные фреймворки: регистрацию маршрута, приём запроса, вызов обработчиков.

Основные методы WebhookManager:

Метод Описание
register(router) Регистрирует роутер с обработчиками событий
unregister() Отменяет регистрацию роутера
setup() Настраивает маршрут вебхука в веб-приложении
shutdown() Очищает ресурсы при остановке

aiosend предоставляет три встроенные реализации:

Менеджер Фреймворк Импорт Установка
AiohttpManager aiohttp aiosend.webhook aiosend (встроен)
FastAPIManager FastAPI aiosend.webhook aiosend[fastapi]
FlaskManager Flask aiosend.webhook aiosend[flask]
🌍

AiohttpManager — вебхуки с aiohttp

AiohttpManager — менеджер вебхуков для фреймворка aiohttp. Он не требует дополнительных зависимостей, так как aiohttp уже используется aiosend для HTTP-запросов.

Конструктор принимает:

  • app: экземпляр aiohttp.web.Application
  • path: путь, на котором будет приниматься вебхук (по умолчанию "/webhook")
  • **kwargs: дополнительные аргументы для post маршрута
Python · aiohttp + aiosend вебхук
import asyncio
from aiohttp import web
from aiosend import CryptoPay
from aiosend.webhook import AiohttpManager

# Создаём aiohttp приложение
app = web.Application()

# Создаём менеджер вебхуков
manager = AiohttpManager(app, path="/webhook")

# Создаём клиента с вебхук-менеджером
cp = CryptoPay(
    token="YOUR_TOKEN",
    webhook_manager=manager,
)

# Регистрируем обработчики (те же декораторы)
@cp.invoice_paid()
async def on_paid(invoice, **kwargs):
    print(f"✅ Инвойс #{invoice.invoice_id} оплачен!")
    print(f"   Сумма: {invoice.amount} {invoice.asset}")
    # Здесь бизнес-логика

@cp.invoice_expired()
async def on_expired(invoice, **kwargs):
    print(f"❌ Инвойс #{invoice.invoice_id} истёк")

async def start_webhook():
    # Устанавливаем вебхук в Crypto Pay
    await cp.set_webhook(url="https://example.com/webhook")
    print("Вебхук установлен!")

app.on_startup.append(lambda _: asyncio.create_task(start_webhook()))

if __name__ == "__main__":
    # Запускаем aiohttp сервер
    web.run_app(app, host="0.0.0.0", port=8080)

Когда Crypto Pay отправляет вебхук на https://example.com/webhook, AiohttpManager принимает запрос, парсит данные и вызывает соответствующий обработчик (on_paid или on_expired) в зависимости от типа события.

⚡

FastAPIManager — вебхуки с FastAPI

FastAPIManager — менеджер вебхуков для FastAPI. Требует установки aiosend[fastapi].

Конструктор принимает:

  • app: экземпляр FastAPI
  • path: путь для вебхука (по умолчанию "/webhook")
Python · FastAPI + aiosend вебхук
# Установка: pip install aiosend[fastapi]

import uvicorn
from fastapi import FastAPI
from aiosend import CryptoPay
from aiosend.webhook import FastAPIManager

# Создаём FastAPI приложение
app = FastAPI()

# Создаём менеджер вебхуков
manager = FastAPIManager(app, path="/webhook")

# Создаём клиента
cp = CryptoPay(
    token="YOUR_TOKEN",
    webhook_manager=manager,
)

@cp.invoice_paid()
async def on_paid(invoice, **kwargs):
    print(f"✅ Получена оплата #{invoice.invoice_id}")
    print(f"   Сумма: {invoice.amount} {invoice.asset}")

@cp.invoice_expired()
async def on_expired(invoice, **kwargs):
    print(f"❌ Инвойс #{invoice.invoice_id} просрочен")

@app.on_event("startup")
async def startup():
    await cp.set_webhook(url="https://example.com/webhook")
    print("Вебхук FastAPI установлен!")

@app.get("/")
async def root():
    return {"status": "ok", "message": "Webhook server running"}

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000)

💡 FastAPI особенности

FastAPIManager автоматически добавляет POST-маршрут /webhook (или кастомный путь) в ваше FastAPI приложение. Вы можете продолжать использовать все возможности FastAPI: Pydantic-модели, Swagger, автодокументацию параллельно с вебхуками.

🧪

FlaskManager — вебхуки с Flask

FlaskManager — менеджер вебхуков для Flask. Требует установки aiosend[flask].

Python · Flask + aiosend вебхук
# Установка: pip install aiosend[flask]

from flask import Flask
from aiosend import SyncCryptoPay
from aiosend.webhook import FlaskManager

# Создаём Flask приложение
app = Flask(__name__)

# Создаём менеджер вебхуков
manager = FlaskManager(app, path="/webhook")

# Создаём синхронного клиента (Flask не async)
cp = SyncCryptoPay(
    token="YOUR_TOKEN",
    webhook_manager=manager,
)

@cp.invoice_paid()
def on_paid(invoice, **kwargs):
    print(f"✅ Инвойс #{invoice.invoice_id} оплачен!")
    print(f"   Сумма: {invoice.amount} {invoice.asset}")

@cp.invoice_expired()
def on_expired(invoice, **kwargs):
    print(f"❌ Инвойс #{invoice.invoice_id} истёк")

@app.route("/")
def index():
    return "Webhook server is running!"

if __name__ == "__main__":
    # Устанавливаем вебхук (синхронно)
    cp.set_webhook(url="https://example.com/webhook")
    print("Вебхук Flask установлен!")
    
    # Запускаем Flask сервер
    app.run(host="0.0.0.0", port=5000, debug=True)

⚠️ Flask + SyncCryptoPay

Flask — синхронный фреймворк, поэтому с ним нужно использовать SyncCryptoPay вместо CryptoPay. Все обработчики также должны быть синхронными (без async def). Если вам нужен асинхронный Flask — используйте quart или переходите на aiohttp/FastAPI.

🔄

WebhookRouter и кастомный WebhookManager

WebhookRouter — это класс, который управляет маршрутизацией входящих вебхуков к соответствующим обработчикам. Он анализирует тип события (invoice_paid, invoice_expired, check_activated, check_expired) и вызывает нужную функцию.

Вы можете создать свой собственный WebhookManager, если хотите интегрировать aiosend с другим веб-фреймворком или добавить кастомную логику:

Python · Кастомный WebhookManager
from aiosend.webhook import WebhookManager, WebhookRouter

class CustomManager(WebhookManager):
    """Кастомный менеджер вебхуков для любого фреймворка."""
    
    def __init__(self, path: str = "/webhook"):
        self.path = path
        self.router = WebhookRouter()
    
    def register(self, router: WebhookRouter) -> None:
        """Регистрирует роутер с обработчиками."""
        self.router = router
    
    def unregister(self) -> None:
        """Отменяет регистрацию."""
        self.router = WebhookRouter()
    
    async def handle_webhook(self, request_data: dict) -> dict:
        """Обрабатывает входящий вебхук.
        
        Вызовите этот метод из вашего веб-обработчика,
        передав тело запроса как словарь.
        """
        update_type = request_data.get("type")
        payload = request_data.get("payload", {})
        
        if update_type == "invoice_paid":
            await self.router.process_invoice_paid(payload)
        elif update_type == "invoice_expired":
            await self.router.process_invoice_expired(payload)
        elif update_type == "check_activated":
            await self.router.process_check_activated(payload)
        elif update_type == "check_expired":
            await self.router.process_check_expired(payload)
        
        return {"ok": True}

# Использование с произвольным веб-фреймворком:
# manager = CustomManager(path="/webhook")
# cp = CryptoPay(token="TOKEN", webhook_manager=manager)
# 
# В вашем веб-обработчике:
# @app.post("/webhook")
# async def webhook_handler(request):
#     data = await request.json()
#     result = await manager.handle_webhook(data)
#     return result
📊

Сравнение WebhookManager'ов

Характеристика AiohttpManager FastAPIManager FlaskManager
Фреймворк aiohttp FastAPI Flask
Async/Sync Async ✅ Async ✅ Sync
Доп. установка Нет (встроен) aiosend[fastapi] aiosend[flask]
Клиент CryptoPay CryptoPay SyncCryptoPay
Обработчики async def async def def (sync)
Популярность Высокая Очень высокая Средняя
Документация API Нет Swagger/OpenAPI ✅ Нет
Когда выбрать Нужен async + минимум зависимостей Новый проект, API, Swagger Существующий Flask-проект
🧩

Полный пример: aiohttp + FastAPI + Flask

Рассмотрим три способа запустить один и тот же вебхук-сервер. Логика обработки одинаковая — меняется только фреймворк.

Python · shared.py — общая логика обработчиков
# shared.py — переиспользуемые обработчики
import logging
from aiosend import CryptoPay

logger = logging.getLogger(__name__)

def register_handlers(cp: CryptoPay):
    """Регистрирует обработчики событий."""
    
    @cp.invoice_paid()
    async def on_paid(invoice, **kwargs):
        logger.info(f"💰 Оплачен инвойс #{invoice.invoice_id}")
        logger.info(f"   Сумма: {invoice.amount} {invoice.asset}")
        logger.info(f"   Payload: {invoice.payload}")
    
    @cp.invoice_expired()
    async def on_expired(invoice, **kwargs):
        logger.warning(f"⏰ Просрочен инвойс #{invoice.invoice_id}")
    
    @cp.check_activated()
    async def on_check_activated(check, **kwargs):
        logger.info(f"🎉 Активирован чек #{check.check_id}")
    
    @cp.check_expired()
    async def on_check_expired(check, **kwargs):
        logger.warning(f"⏰ Истёк чек #{check.check_id}")
Python · main_aiohttp.py
import asyncio
from aiohttp import web
from aiosend import CryptoPay
from aiosend.webhook import AiohttpManager
from shared import register_handlers

app = web.Application()
manager = AiohttpManager(app, path="/webhook")

cp = CryptoPay(token="YOUR_TOKEN", webhook_manager=manager)
register_handlers(cp)

async def on_startup(app):
    await cp.set_webhook(url="https://example.com/webhook")
    print("Вебхук aiohttp установлен!")

app.on_startup.append(on_startup)

if __name__ == "__main__":
    web.run_app(app, host="0.0.0.0", port=8080)
Python · main_fastapi.py
import uvicorn
from fastapi import FastAPI
from aiosend import CryptoPay
from aiosend.webhook import FastAPIManager
from shared import register_handlers

app = FastAPI()
manager = FastAPIManager(app, path="/webhook")

cp = CryptoPay(token="YOUR_TOKEN", webhook_manager=manager)
register_handlers(cp)

@app.on_event("startup")
async def startup():
    await cp.set_webhook(url="https://example.com/webhook")
    print("Вебхук FastAPI установлен!")

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000)
Python · main_flask.py
from flask import Flask
from aiosend import SyncCryptoPay
from aiosend.webhook import FlaskManager

app = Flask(__name__)
manager = FlaskManager(app, path="/webhook")

cp = SyncCryptoPay(token="YOUR_TOKEN", webhook_manager=manager)

# Регистрируем обработчики (синхронные)
@cp.invoice_paid()
def on_paid(invoice, **kwargs):
    print(f"💰 Оплачен инвойс #{invoice.invoice_id}")

@cp.invoice_expired()
def on_expired(invoice, **kwargs):
    print(f"⏰ Просрочен инвойс #{invoice.invoice_id}")

if __name__ == "__main__":
    cp.set_webhook(url="https://example.com/webhook")
    print("Вебхук Flask установлен!")
    app.run(host="0.0.0.0", port=5000)
🧩

Дополнительные сценарии с вебхуками

Рассмотрим продвинутые сценарии использования вебхуков в aiosend: комбинирование с polling, верификация запросов, работа с несколькими клиентами.

Пример: Верификация входящих вебхуков

Для безопасности стоит проверять, что вебхук действительно пришёл от Crypto Pay. Хотя aiosend делает это автоматически, вы можете добавить дополнительную проверку:

Python · Верификация вебхука с помощью кастомного менеджера
import hmac
import hashlib
from aiosend.webhook import WebhookManager, WebhookRouter

class VerifiedWebhookManager(WebhookManager):
    """Менеджер вебхуков с проверкой подписи."""
    
    def __init__(self, app, path: str, secret: str):
        self.app = app
        self.path = path
        self.secret = secret.encode()
        self.router = WebhookRouter()
    
    def _verify_signature(self, body: bytes, signature: str) -> bool:
        """Проверяет HMAC-подпись запроса."""
        expected = hmac.new(
            self.secret,
            body,
            hashlib.sha256,
        ).hexdigest()
        return hmac.compare_digest(expected, signature)
    
    def register(self, router: WebhookRouter) -> None:
        self.router = router
    
    def unregister(self) -> None:
        self.router = WebhookRouter()
    
    async def handle_webhook(self, request):
        body = await request.read()
        signature = request.headers.get("X-Crypto-Pay-Signature", "")
        
        if not self._verify_signature(body, signature):
            return {"ok": False, "error": "Invalid signature"}
        
        import json
        data = json.loads(body)
        update_type = data.get("type")
        payload = data.get("payload", {})
        
        # Обработка событий
        if update_type == "invoice_paid":
            await self.router.process_invoice_paid(payload)
        elif update_type == "invoice_expired":
            await self.router.process_invoice_expired(payload)
        
        return {"ok": True}
    
    def setup(self):
        from aiohttp import web
        self.app.router.add_post(
            self.path,
            self.handle_webhook,
        )
    
    def shutdown(self):
        pass

Пример: Несколько клиентов с вебхуками

Вы можете запустить несколько клиентов на одном сервере, каждый со своим вебхуком:

Python · Два клиента на одном сервере
import asyncio
from aiohttp import web
from aiosend import CryptoPay
from aiosend.webhook import AiohttpManager

app = web.Application()

# Первый клиент — mainnet
manager1 = AiohttpManager(app, path="/webhook/mainnet")
cp1 = CryptoPay(
    token="MAINNET_TOKEN",
    webhook_manager=manager1,
)

@cp1.invoice_paid()
async def on_paid_mainnet(invoice, **kwargs):
    print(f"[MAINNET] Оплата: {invoice.amount} {invoice.asset}")

# Второй клиент — testnet
manager2 = AiohttpManager(app, path="/webhook/testnet")
cp2 = CryptoPay(
    token="TESTNET_TOKEN",
    webhook_manager=manager2,
)

@cp2.invoice_paid()
async def on_paid_testnet(invoice, **kwargs):
    print(f"[TESTNET] Оплата: {invoice.amount} {invoice.asset}")

async def start():
    await cp1.set_webhook(url="https://example.com/webhook/mainnet")
    await cp2.set_webhook(url="https://example.com/webhook/testnet")
    print("Оба вебхука установлены!")

app.on_startup.append(lambda _: asyncio.create_task(start()))

if __name__ == "__main__":
    web.run_app(app, host="0.0.0.0", port=8080)

Пример: Комбинирование вебхуков и polling

Можно использовать вебхуки как основной механизм, а polling — как запасной (fallback):

Python · Вебхуки + polling fallback
import asyncio
from aiohttp import web
from aiosend import CryptoPay
from aiosend.webhook import AiohttpManager
from aiosend.polling import PollingConfig

app = web.Application()
manager = AiohttpManager(app, path="/webhook")

cp = CryptoPay(
    token="YOUR_TOKEN",
    webhook_manager=manager,
    polling_config=PollingConfig(timeout=300, delay=5),
)

processed_ids = set()

@cp.invoice_paid()
async def on_paid(invoice, **kwargs):
    if invoice.invoice_id in processed_ids:
        return  # уже обработано через webhook
    processed_ids.add(invoice.invoice_id)
    print(f"Обработано: {invoice.invoice_id}")

async def start_webhook_and_polling():
    # Устанавливаем вебхук
    await cp.set_webhook(url="https://example.com/webhook")
    
    # Запускаем polling как fallback (с меньшей частотой)
    # polling будет обрабатывать события, которые
    # по какой-то причине не пришли через webhook
    await cp.start_polling(reset_webhook=False)

app.on_startup.append(
    lambda _: asyncio.create_task(start_webhook_and_polling())
)

if __name__ == "__main__":
    web.run_app(app, host="0.0.0.0", port=8080)
📌

Что важно запомнить

1️⃣
Вебхуки — обратные вызовы от Crypto Pay. API сам отправляет POST на ваш сервер при событии. Мгновенно, без polling.
2️⃣
Три менеджера из коробки: AiohttpManager, FastAPIManager, FlaskManager. Выберите под ваш фреймворк.
3️⃣
Вебхук включается в @CryptoBot. URL должен быть HTTPS. В коде установка через cp.set_webhook(url).
4️⃣
Декораторы работают и для вебхуков. @cp.invoice_paid() и другие — едины для обоих режимов. Меняется только способ доставки событий.
5️⃣
Кастомный WebhookManager. Создайте свой менеджер, если нужен нестандартный фреймворк или особая логика обработки.
💻

Практическая задача

Задача: Вебхук-сервер для приёма платежей

Напишите вебхук-сервер на FastAPI (файл webhook_server.py), который:

  1. Использует FastAPIManager для приёма вебхуков на пути /crypto-pay.
  2. Регистрирует обработчики: invoice_paid, invoice_expired, check_activated, check_expired.
  3. В обработчике paid — логирует и сохраняет данные оплаты в JSON-файл payments.log.
  4. Добавляет эндпоинт GET /stats, который возвращает статистику: количество оплат, сумму, количество чеков.
  5. При старте устанавливает вебхук через cp.set_webhook().
  6. Обрабатывает graceful shutdown: удаляет вебхук через cp.delete_webhook().

Ожидаемая структура проекта:

webhook_server.py
payments.log  (создаётся автоматически)
.env          (CRYPTOPAY_TOKEN, WEBHOOK_URL)

Урок 6.3: Webhook обработка

8 вопросов